1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 12:02:54 +08:00

Merge branch 'master' into osu-target-mod

This commit is contained in:
Henry Lin 2021-07-03 13:00:52 +08:00 committed by GitHub
commit f18c17d1a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1075 additions and 188 deletions

View File

@ -12,7 +12,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<ItemGroup>

View File

@ -12,7 +12,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<ItemGroup>

View File

@ -12,7 +12,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<ItemGroup>

View File

@ -12,7 +12,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<ItemGroup>

View File

@ -51,11 +51,11 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.618.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.630.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.701.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.702.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
<PackageReference Include="Realm" Version="10.2.0" />
<PackageReference Include="Realm" Version="10.2.1" />
</ItemGroup>
</Project>

View File

@ -116,7 +116,7 @@ namespace osu.Desktop.Updater
if (scheduleRecheck)
{
// check again in 30 minutes.
Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30);
Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
}
}
@ -141,7 +141,7 @@ namespace osu.Desktop.Updater
Activated = () =>
{
updateManager.PrepareUpdateAsync()
.ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
.ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit()));
return true;
};
}

View File

@ -9,7 +9,7 @@
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">

View File

@ -5,7 +5,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">

View File

@ -0,0 +1,16 @@
// 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.
namespace osu.Game.Rulesets.Osu.Mods
{
/// <summary>
/// Marker interface for any mod which completely hides the approach circles.
/// Used for incompatibility with <see cref="IRequiresApproachCircles"/>.
/// </summary>
/// <remarks>
/// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour.
/// </remarks>
public interface IHidesApproachCircles
{
}
}

View File

@ -1,12 +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.
namespace osu.Game.Rulesets.Osu.Mods
{
/// <summary>
/// Any mod which affects the animation or visibility of approach circles. Should be used for incompatibility purposes.
/// </summary>
public interface IMutateApproachCircles
{
}
}

View File

@ -0,0 +1,16 @@
// 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.
namespace osu.Game.Rulesets.Osu.Mods
{
/// <summary>
/// Marker interface for any mod which requires the approach circles to be visible.
/// Used for incompatibility with <see cref="IHidesApproachCircles"/>.
/// </summary>
/// <remarks>
/// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour.
/// </remarks>
public interface IRequiresApproachCircles
{
}
}

View File

@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IMutateApproachCircles
public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IRequiresApproachCircles
{
public override string Name => "Approach Different";
public override string Acronym => "AD";
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
public BindableFloat Scale { get; } = new BindableFloat(4)

View File

@ -15,12 +15,12 @@ using osu.Game.Rulesets.Osu.Skinning;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModHidden : ModHidden, IMutateApproachCircles
public class OsuModHidden : ModHidden, IHidesApproachCircles
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
/// <summary>
/// Adjusts the size of hit objects during their fade in animation.
/// </summary>
public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IMutateApproachCircles
public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IHidesApproachCircles
{
public override ModType Type => ModType.Fun;
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
protected virtual float EndScale => 1;
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{

View File

@ -12,7 +12,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModSpinIn : ModWithVisibilityAdjustment, IMutateApproachCircles
public class OsuModSpinIn : ModWithVisibilityAdjustment, IHidesApproachCircles
{
public override string Name => "Spin In";
public override string Acronym => "SI";
@ -21,8 +21,9 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1;
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
// todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque,
// further implementation will be required for supporting that.
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) };
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;

View File

@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModTraceable : ModWithVisibilityAdjustment, IMutateApproachCircles
public class OsuModTraceable : ModWithVisibilityAdjustment, IRequiresApproachCircles
{
public override string Name => "Traceable";
public override string Acronym => "TC";
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">

View File

@ -0,0 +1,121 @@
// 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.Screens;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Online.Solo;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePlayerScoreSubmission : OsuPlayerTestScene
{
protected override bool AllowFail => allowFail;
private bool allowFail;
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
protected override bool HasCustomSteps => true;
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
[Test]
public void TestNoSubmissionOnResultsWithNoToken()
{
prepareTokenResponse(false);
CreateTest(() => allowFail = false);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
}
[Test]
public void TestSubmissionOnResults()
{
prepareTokenResponse(true);
CreateTest(() => allowFail = false);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
}
[Test]
public void TestNoSubmissionOnExitWithNoToken()
{
prepareTokenResponse(false);
CreateTest(() => allowFail = false);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddStep("exit", () => Player.Exit());
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
}
[Test]
public void TestSubmissionOnFail()
{
prepareTokenResponse(true);
CreateTest(() => allowFail = true);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("exit", () => Player.Exit());
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
}
[Test]
public void TestSubmissionOnExit()
{
prepareTokenResponse(true);
CreateTest(() => allowFail = false);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddStep("exit", () => Player.Exit());
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
}
private void prepareTokenResponse(bool validToken)
{
AddStep("Prepare test API", () =>
{
dummyAPI.HandleRequest = request =>
{
switch (request)
{
case CreateSoloScoreRequest tokenRequest:
if (validToken)
tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 });
else
tokenRequest.TriggerFailure(new APIException("something went wrong!", null));
return true;
}
return false;
};
});
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -65,18 +66,22 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
OnReadyClick = async () =>
OnReadyClick = () =>
{
readyClickOperation = OngoingOperationTracker.BeginOperation();
if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
Task.Run(async () =>
{
await Client.StartMatch();
return;
}
if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
{
await Client.StartMatch();
return;
}
await Client.ToggleReady();
readyClickOperation.Dispose();
await Client.ToggleReady();
readyClickOperation.Dispose();
});
}
});
});

View File

@ -3,6 +3,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -69,11 +70,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
OnSpectateClick = async () =>
OnSpectateClick = () =>
{
readyClickOperation = OngoingOperationTracker.BeginOperation();
await Client.ToggleSpectate();
readyClickOperation.Dispose();
Task.Run(async () =>
{
await Client.ToggleSpectate();
readyClickOperation.Dispose();
});
}
},
readyButton = new MultiplayerReadyButton
@ -81,18 +86,22 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
OnReadyClick = async () =>
OnReadyClick = () =>
{
readyClickOperation = OngoingOperationTracker.BeginOperation();
if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
Task.Run(async () =>
{
await Client.StartMatch();
return;
}
if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
{
await Client.StartMatch();
return;
}
await Client.ToggleReady();
readyClickOperation.Dispose();
await Client.ToggleReady();
readyClickOperation.Dispose();
});
}
}
}

View File

@ -0,0 +1,40 @@
// 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.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
SelectedRoom.Value = new Room();
Child = new StarRatingRangeDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
});
[Test]
public void TestRange([Values(0, 2, 3, 4, 6, 7)] double min, [Values(0, 2, 3, 4, 6, 7)] double max)
{
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.AddRange(new[]
{
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = min } } },
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = max } } },
});
});
}
}
}

View File

@ -11,6 +11,7 @@ using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps.IO;
using osuTK.Input;
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
@ -57,8 +58,11 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestPerformAtSongSelectFromPlayerLoader()
{
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
PushAndConfirm(() => new TestPlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
AddStep("Press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
@ -68,8 +72,11 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestPerformAtMenuFromPlayerLoader()
{
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
PushAndConfirm(() => new TestPlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
AddStep("Press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -9,6 +10,9 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers.Markdown;
using osu.Game.Overlays;
using osu.Game.Overlays.Wiki.Markdown;
@ -102,7 +106,7 @@ needs_cleanup: true
{
AddStep("Add absolute image", () =>
{
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
});
}
@ -112,8 +116,7 @@ needs_cleanup: true
{
AddStep("Add relative image", () =>
{
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/";
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
});
}
@ -123,8 +126,7 @@ needs_cleanup: true
{
AddStep("Add paragraph with block image", () =>
{
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/";
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
markdownContainer.Text = @"Line before image
![play menu](img/play-menu.jpg ""Main Menu in osu!"")
@ -138,7 +140,7 @@ Line after image";
{
AddStep("Add inline image", () =>
{
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!";
});
}
@ -148,7 +150,7 @@ Line after image";
{
AddStep("Add Table", () =>
{
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = @"
| Image | Name | Effect |
| :-: | :-: | :-- |
@ -162,15 +164,33 @@ Line after image";
});
}
[Test]
public void TestWideImageNotExceedContainer()
{
AddStep("Add image", () =>
{
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/";
markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")";
});
AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType<DelayedLoadWrapper>().First().DelayedLoadCompleted);
AddStep("Change container width", () =>
{
markdownContainer.Width = 0.5f;
});
AddAssert("Image not exceed container width", () =>
{
var spriteImage = markdownContainer.ChildrenOfType<Sprite>().First();
return Precision.DefinitelyBigger(markdownContainer.DrawWidth, spriteImage.DrawWidth);
});
}
private class TestMarkdownContainer : WikiMarkdownContainer
{
public LinkInline Link;
public new string DocumentUrl
{
set => base.DocumentUrl = value;
}
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
{
UrlAdded = link => Link = link,

View File

@ -0,0 +1,91 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneColourPicker : OsuTestScene
{
private readonly Bindable<Colour4> colour = new Bindable<Colour4>(Colour4.Aquamarine);
[SetUpSteps]
public void SetUpSteps()
{
AddStep("create pickers", () => Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Dimension(),
new Dimension()
},
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = @"No OverlayColourProvider",
Font = OsuFont.Default.With(size: 40)
},
new OsuColourPicker
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = { BindTarget = colour },
}
}
},
new ColourProvidingContainer(OverlayColourScheme.Blue)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = @"With blue OverlayColourProvider",
Font = OsuFont.Default.With(size: 40)
},
new OsuColourPicker
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = { BindTarget = colour },
}
}
}
}
}
});
AddStep("set green", () => colour.Value = Colour4.LimeGreen);
AddStep("set white", () => colour.Value = Colour4.White);
AddStep("set red", () => colour.Value = Colour4.Red);
}
private class ColourProvidingContainer : Container
{
[Cached]
private OverlayColourProvider provider { get; }
public ColourProvidingContainer(OverlayColourScheme colourScheme)
{
provider = new OverlayColourProvider(colourScheme);
}
}
}
}

View File

@ -5,7 +5,7 @@
<PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup>

View File

@ -7,7 +7,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>

View File

@ -32,10 +32,10 @@ namespace osu.Game.Graphics
return Pink;
case DifficultyRating.Expert:
return useLighterColour ? PurpleLight : Purple;
return PurpleLight;
case DifficultyRating.ExpertPlus:
return useLighterColour ? Gray9 : Gray0;
return useLighterColour ? Gray9 : Color4Extensions.FromHex("#121415");
}
}

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
namespace osu.Game.Graphics.UserInterface
{
@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuColour colours)
{
sample = audio.Samples.Get(@"UI/sliderbar-notch");
sample = audio.Samples.Get(@"UI/notch-tick");
AccentColour = colours.Pink;
}
@ -149,7 +150,7 @@ namespace osu.Game.Graphics.UserInterface
private void playSample(T value)
{
if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50)
if (Clock == null || Clock.CurrentTime - lastSampleTime <= 30)
return;
if (value.Equals(lastSampleValue))
@ -158,13 +159,15 @@ namespace osu.Game.Graphics.UserInterface
lastSampleValue = value;
lastSampleTime = Clock.CurrentTime;
var channel = sample.Play();
var channel = sample.GetChannel();
channel.Frequency.Value = 1 + NormalizedValue * 0.2f;
if (NormalizedValue == 0)
channel.Frequency.Value -= 0.4f;
else if (NormalizedValue == 1)
channel.Frequency.Value += 0.4f;
channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f;
// intentionally pitched down, even when hitting max.
if (NormalizedValue == 0 || NormalizedValue == 1)
channel.Frequency.Value -= 0.5f;
channel.Play();
}
private void updateTooltipText(T value)

View File

@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class OsuColourPicker : ColourPicker
{
public OsuColourPicker()
{
CornerRadius = 10;
Masking = true;
}
protected override HSVColourPicker CreateHSVColourPicker() => new OsuHSVColourPicker();
protected override HexColourPicker CreateHexColourPicker() => new OsuHexColourPicker();
}
}

View File

@ -0,0 +1,129 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class OsuHSVColourPicker : HSVColourPicker
{
private const float spacing = 10;
private const float corner_radius = 10;
private const float control_border_thickness = 3;
protected override HueSelector CreateHueSelector() => new OsuHueSelector();
protected override SaturationValueSelector CreateSaturationValueSelector() => new OsuSaturationValueSelector();
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour)
{
Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeafoamDark;
Content.Padding = new MarginPadding(spacing);
Content.Spacing = new Vector2(0, spacing);
}
private static EdgeEffectParameters createShadowParameters() => new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0, 1),
Radius = 3,
Colour = Colour4.Black.Opacity(0.3f)
};
private class OsuHueSelector : HueSelector
{
public OsuHueSelector()
{
SliderBar.CornerRadius = corner_radius;
SliderBar.Masking = true;
}
protected override Drawable CreateSliderNub() => new SliderNub(this);
private class SliderNub : CompositeDrawable
{
private readonly Bindable<float> hue;
private readonly Box fill;
public SliderNub(OsuHueSelector osuHueSelector)
{
hue = osuHueSelector.Hue.GetBoundCopy();
InternalChild = new CircularContainer
{
Height = 35,
Width = 10,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Masking = true,
BorderColour = Colour4.White,
BorderThickness = control_border_thickness,
EdgeEffect = createShadowParameters(),
Child = fill = new Box
{
RelativeSizeAxes = Axes.Both
}
};
}
protected override void LoadComplete()
{
hue.BindValueChanged(h => fill.Colour = Colour4.FromHSV(h.NewValue, 1, 1), true);
}
}
}
private class OsuSaturationValueSelector : SaturationValueSelector
{
public OsuSaturationValueSelector()
{
SelectionArea.CornerRadius = corner_radius;
SelectionArea.Masking = true;
// purposefully use hard non-AA'd masking to avoid edge artifacts.
SelectionArea.MaskingSmoothness = 0;
}
protected override Marker CreateMarker() => new OsuMarker();
private class OsuMarker : Marker
{
private readonly Box previewBox;
public OsuMarker()
{
AutoSizeAxes = Axes.Both;
InternalChild = new CircularContainer
{
Size = new Vector2(20),
Masking = true,
BorderColour = Colour4.White,
BorderThickness = control_border_thickness,
EdgeEffect = createShadowParameters(),
Child = previewBox = new Box
{
RelativeSizeAxes = Axes.Both
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(colour => previewBox.Colour = colour.NewValue, true);
}
}
}
}
}

View File

@ -0,0 +1,57 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class OsuHexColourPicker : HexColourPicker
{
public OsuHexColourPicker()
{
Padding = new MarginPadding(20);
Spacing = 20;
}
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour osuColour)
{
Background.Colour = overlayColourProvider?.Dark6 ?? osuColour.GreySeafoamDarker;
}
protected override TextBox CreateHexCodeTextBox() => new OsuTextBox();
protected override ColourPreview CreateColourPreview() => new OsuColourPreview();
private class OsuColourPreview : ColourPreview
{
private readonly Box preview;
public OsuColourPreview()
{
InternalChild = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Child = preview = new Box
{
RelativeSizeAxes = Axes.Both
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(colour => preview.Colour = colour.NewValue, true);
}
}
}
}

View File

@ -223,7 +223,20 @@ namespace osu.Game
// bind config int to database RulesetInfo
configRuleset = LocalConfig.GetBindable<int>(OsuSetting.Ruleset);
Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First();
var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value);
try
{
Ruleset.Value = preferredRuleset ?? RulesetStore.AvailableRulesets.First();
}
catch (Exception e)
{
// on startup, a ruleset may be selected which has compatibility issues.
Logger.Error(e, $@"Failed to switch to preferred ruleset {preferredRuleset}.");
Ruleset.Value = RulesetStore.AvailableRulesets.First();
}
Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0;
// bind config int to database SkinInfo
@ -478,6 +491,10 @@ namespace osu.Game
public override Task Import(params ImportTask[] imports)
{
// encapsulate task as we don't want to begin the import process until in a ready state.
// ReSharper disable once AsyncVoidLambda
// TODO: This is bad because `new Task` doesn't have a Func<Task?> override.
// Only used for android imports and a bit of a mess. Probably needs rethinking overall.
var importTask = new Task(async () => await base.Import(imports).ConfigureAwait(false));
waitForReady(() => this, _ => importTask.Start());

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using Humanizer;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osuTK.Graphics;
@ -17,11 +18,11 @@ namespace osu.Game.Overlays.Changelog
Width *= 2;
}
protected override string MainText => Value.DisplayName;
protected override LocalisableString MainText => Value.DisplayName;
protected override string AdditionalText => Value.LatestBuild.DisplayVersion;
protected override LocalisableString AdditionalText => Value.LatestBuild.DisplayVersion;
protected override string InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null;
protected override LocalisableString InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null;
protected override Color4 GetBarColour(OsuColour colours) => Value.Colour;
}

View File

@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Dashboard
{
@ -13,13 +16,14 @@ namespace osu.Game.Overlays.Dashboard
{
public DashboardTitle()
{
Title = "dashboard";
Title = HomeStrings.UserTitle;
Description = "view your friends and other information";
IconTexture = "Icons/Hexacons/social";
}
}
}
[LocalisableEnum(typeof(DashboardOverlayTabsEnumLocalisationMapper))]
public enum DashboardOverlayTabs
{
Friends,
@ -27,4 +31,22 @@ namespace osu.Game.Overlays.Dashboard
[Description("Currently Playing")]
CurrentlyPlaying
}
public class DashboardOverlayTabsEnumLocalisationMapper : EnumLocalisationMapper<DashboardOverlayTabs>
{
public override LocalisableString Map(DashboardOverlayTabs value)
{
switch (value)
{
case DashboardOverlayTabs.Friends:
return FriendsStrings.TitleCompact;
case DashboardOverlayTabs.CurrentlyPlaying:
return @"Currently Playing";
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
}

View File

@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Extensions;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osuTK.Graphics;
@ -14,9 +16,9 @@ namespace osu.Game.Overlays.Dashboard.Friends
{
}
protected override string MainText => Value.Status.ToString();
protected override LocalisableString MainText => Value.Status.GetLocalisableDescription();
protected override string AdditionalText => Value.Count.ToString();
protected override LocalisableString AdditionalText => Value.Count.ToString();
protected override Color4 GetBarColour(OsuColour colours)
{

View File

@ -1,12 +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;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Dashboard.Friends
{
[LocalisableEnum(typeof(OnlineStatusEnumLocalisationMapper))]
public enum OnlineStatus
{
All,
Online,
Offline
}
public class OnlineStatusEnumLocalisationMapper : EnumLocalisationMapper<OnlineStatus>
{
public override LocalisableString Map(OnlineStatus value)
{
switch (value)
{
case OnlineStatus.All:
return SortStrings.All;
case OnlineStatus.Online:
return UsersStrings.StatusOnline;
case OnlineStatus.Offline:
return UsersStrings.StatusOffline;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
}

View File

@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Dashboard.Friends
{
@ -9,6 +12,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
{
}
[LocalisableEnum(typeof(UserSortCriteriaEnumLocalisationMappper))]
public enum UserSortCriteria
{
[Description(@"Recently Active")]
@ -16,4 +20,25 @@ namespace osu.Game.Overlays.Dashboard.Friends
Rank,
Username
}
public class UserSortCriteriaEnumLocalisationMappper : EnumLocalisationMapper<UserSortCriteria>
{
public override LocalisableString Map(UserSortCriteria value)
{
switch (value)
{
case UserSortCriteria.LastVisit:
return SortStrings.LastVisit;
case UserSortCriteria.Rank:
return SortStrings.Rank;
case UserSortCriteria.Username:
return SortStrings.Username;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
}

View File

@ -12,6 +12,9 @@ using osu.Framework.Allocation;
using osuTK.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using System;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Extensions;
namespace osu.Game.Overlays
{
@ -57,7 +60,7 @@ namespace osu.Game.Overlays
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
public LocalisableString TooltipText => $@"{Value} view";
public LocalisableString TooltipText => Value.GetLocalisableDescription();
private readonly SpriteIcon icon;
@ -98,10 +101,32 @@ namespace osu.Game.Overlays
}
}
[LocalisableEnum(typeof(OverlayPanelDisplayStyleEnumLocalisationMapper))]
public enum OverlayPanelDisplayStyle
{
Card,
List,
Brick
}
public class OverlayPanelDisplayStyleEnumLocalisationMapper : EnumLocalisationMapper<OverlayPanelDisplayStyle>
{
public override LocalisableString Map(OverlayPanelDisplayStyle value)
{
switch (value)
{
case OverlayPanelDisplayStyle.Card:
return UsersStrings.ViewModeCard;
case OverlayPanelDisplayStyle.List:
return UsersStrings.ViewModeList;
case OverlayPanelDisplayStyle.Brick:
return UsersStrings.ViewModeBrick;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;
@ -118,7 +119,7 @@ namespace osu.Game.Overlays
}
});
TooltipText = "Scroll to top";
TooltipText = CommonStrings.ButtonsBackToTop;
}
[BackgroundDependencyLoader]

View File

@ -12,6 +12,7 @@ using osu.Framework.Allocation;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics;
using osuTK.Graphics;
using osu.Framework.Localisation;
namespace osu.Game.Overlays
{
@ -88,11 +89,11 @@ namespace osu.Game.Overlays
SelectedItem.BindValueChanged(_ => updateState(), true);
}
protected abstract string MainText { get; }
protected abstract LocalisableString MainText { get; }
protected abstract string AdditionalText { get; }
protected abstract LocalisableString AdditionalText { get; }
protected virtual string InfoText => string.Empty;
protected virtual LocalisableString InfoText => string.Empty;
protected abstract Color4 GetBarColour(OsuColour colours);

View File

@ -4,6 +4,8 @@
using System;
using System.Globalization;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -36,6 +38,9 @@ namespace osu.Game.Overlays.Volume
private OsuSpriteText text;
private BufferedContainer maxGlow;
private Sample sample;
private double sampleLastPlaybackTime;
public VolumeMeter(string name, float circleSize, Color4 meterColour)
{
this.circleSize = circleSize;
@ -46,8 +51,11 @@ namespace osu.Game.Overlays.Volume
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OsuColour colours, AudioManager audio)
{
sample = audio.Samples.Get(@"UI/notch-tick");
sampleLastPlaybackTime = Time.Current;
Color4 backgroundColour = colours.Gray1;
CircularProgress bgProgress;
@ -178,22 +186,12 @@ namespace osu.Game.Overlays.Volume
}
};
Bindable.ValueChanged += volume =>
{
this.TransformTo("DisplayVolume",
volume.NewValue,
400,
Easing.OutQuint);
};
Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true);
bgProgress.Current.Value = 0.75f;
}
protected override void LoadComplete()
{
base.LoadComplete();
Bindable.TriggerChange();
}
private int displayVolumeInt;
private double displayVolume;
@ -202,8 +200,16 @@ namespace osu.Game.Overlays.Volume
get => displayVolume;
set
{
if (value == displayVolume)
return;
displayVolume = value;
int intValue = (int)Math.Round(displayVolume * 100);
bool intVolumeChanged = intValue != displayVolumeInt;
displayVolumeInt = intValue;
if (displayVolume >= 0.995f)
{
text.Text = "MAX";
@ -212,14 +218,36 @@ namespace osu.Game.Overlays.Volume
else
{
maxGlow.EffectColour = Color4.Transparent;
text.Text = Math.Round(displayVolume * 100).ToString(CultureInfo.CurrentCulture);
text.Text = displayVolumeInt.ToString(CultureInfo.CurrentCulture);
}
volumeCircle.Current.Value = displayVolume * 0.75f;
volumeCircleGlow.Current.Value = displayVolume * 0.75f;
if (intVolumeChanged && IsLoaded)
Scheduler.AddOnce(playTickSound);
}
}
private void playTickSound()
{
const int tick_debounce_time = 30;
if (Time.Current - sampleLastPlaybackTime <= tick_debounce_time)
return;
var channel = sample.GetChannel();
channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f;
// intentionally pitched down, even when hitting max.
if (displayVolumeInt == 0 || displayVolumeInt == 100)
channel.Frequency.Value -= 0.5f;
channel.Play();
sampleLastPlaybackTime = Time.Current;
}
public double Volume
{
get => Bindable.Value;

View File

@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Sprites;
using osuTK;
namespace osu.Game.Overlays.Wiki.Markdown
@ -32,11 +33,7 @@ namespace osu.Game.Overlays.Wiki.Markdown
{
Children = new Drawable[]
{
new WikiMarkdownImage(linkInline)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
new BlockMarkdownImage(linkInline),
parentTextComponent.CreateSpriteText().With(t =>
{
t.Text = linkInline.Title;
@ -45,5 +42,50 @@ namespace osu.Game.Overlays.Wiki.Markdown
}),
};
}
private class BlockMarkdownImage : WikiMarkdownImage
{
public BlockMarkdownImage(LinkInline linkInline)
: base(linkInline)
{
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
}
protected override ImageContainer CreateImageContainer(string url) => new BlockImageContainer(url);
private class BlockImageContainer : ImageContainer
{
public BlockImageContainer(string url)
: base(url)
{
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
}
protected override Sprite CreateImageSprite() => new ImageSprite();
private class ImageSprite : Sprite
{
public ImageSprite()
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
}
protected override void Update()
{
base.Update();
if (Width > Parent.DrawWidth)
{
float ratio = Height / Width;
Width = Parent.DrawWidth;
Height = ratio * Width;
}
}
}
}
}
}
}

View File

@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual bool UserPlayable => true;
[Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009
public virtual bool IsRanked => false;
[Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009
public virtual bool Ranked => false;
/// <summary>
/// Whether this mod requires configuration to apply changes to the game.

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -36,6 +37,8 @@ namespace osu.Game.Screens.Menu
private readonly Bindable<User> currentUser = new Bindable<User>();
private FillFlowContainer fill;
private readonly List<Drawable> expendableText = new List<Drawable>();
public Disclaimer(OsuScreen nextScreen = null)
{
this.nextScreen = nextScreen;
@ -54,7 +57,7 @@ namespace osu.Game.Screens.Menu
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Flask,
Icon = OsuIcon.Logo,
Size = new Vector2(icon_size),
Y = icon_y,
},
@ -70,37 +73,55 @@ namespace osu.Game.Screens.Menu
{
textFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
Width = 680,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Spacing = new Vector2(0, 2),
LayoutDuration = 2000,
LayoutEasing = Easing.OutQuint
},
supportFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Alpha = 0,
Spacing = new Vector2(0, 2),
},
}
}
},
supportFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Padding = new MarginPadding(20),
Alpha = 0,
Spacing = new Vector2(0, 2),
},
};
textFlow.AddText("This project is an ongoing ", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Light));
textFlow.AddText("work in progress", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.SemiBold));
textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular));
expendableText.AddRange(textFlow.AddText("lazer", t =>
{
t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular);
t.Colour = colours.PinkLight;
}));
static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular);
static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold);
textFlow.NewParagraph();
static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold);
textFlow.AddText("the next ", formatRegular);
textFlow.AddText("major update", t =>
{
t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold);
t.Colour = colours.Pink;
});
expendableText.AddRange(textFlow.AddText(" coming to osu!", formatRegular));
textFlow.AddText(".", formatRegular);
textFlow.AddParagraph(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold));
textFlow.NewParagraph();
textFlow.NewParagraph();
textFlow.AddParagraph("today's tip:", formatSemiBold);
textFlow.AddParagraph(getRandomTip(), formatRegular);
textFlow.NewParagraph();
textFlow.NewParagraph();
@ -116,19 +137,19 @@ namespace osu.Game.Screens.Menu
if (e.NewValue.IsSupporter)
{
supportFlow.AddText("Eternal thanks to you for supporting osu!", format);
supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold);
}
else
{
supportFlow.AddText("Consider becoming an ", format);
supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format);
supportFlow.AddText(" to help support the game", format);
supportFlow.AddText("Consider becoming an ", formatSemiBold);
supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold);
supportFlow.AddText(" to help support osu!'s development", formatSemiBold);
}
heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t =>
{
t.Padding = new MarginPadding { Left = 5, Top = 3 };
t.Font = t.Font.With(size: 12);
t.Font = t.Font.With(size: 20);
t.Origin = Anchor.Centre;
t.Colour = colours.Pink;
}).First();
@ -169,7 +190,15 @@ namespace osu.Game.Screens.Menu
.MoveToY(icon_y, 160, Easing.InQuart)
.FadeColour(Color4.White, 160);
fill.Delay(520 + 160).MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart);
using (BeginDelayedSequence(520 + 160))
{
fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart);
Schedule(() => expendableText.ForEach(t =>
{
t.FadeOut(100);
t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart);
}));
}
}
supportFlow.FadeOut().Delay(2000).FadeIn(500);

View File

@ -0,0 +1,93 @@
// 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.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Screens.Ranking.Expanded;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Components
{
public class StarRatingRangeDisplay : OnlinePlayComposite
{
[Resolved]
private OsuColour colours { get; set; }
private StarRatingDisplay minDisplay;
private Drawable minBackground;
private StarRatingDisplay maxDisplay;
private Drawable maxBackground;
public StarRatingRangeDisplay()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 1,
Children = new[]
{
minBackground = new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
},
maxBackground = new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
minDisplay = new StarRatingDisplay(default),
maxDisplay = new StarRatingDisplay(default)
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Playlist.BindCollectionChanged(updateRange, true);
}
private void updateRange(object sender, NotifyCollectionChangedEventArgs e)
{
var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarDifficulty).ToArray();
StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarDifficulty : 0, 0);
StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarDifficulty : 0, 0);
minDisplay.Current.Value = minDifficulty;
maxDisplay.Current.Value = maxDifficulty;
minBackground.Colour = colours.ForDifficultyRating(minDifficulty.DifficultyRating, true);
maxBackground.Colour = colours.ForDifficultyRating(maxDifficulty.DifficultyRating, true);
}
}
}

View File

@ -768,6 +768,7 @@ namespace osu.Game.Screens.Play
return false;
HasFailed = true;
Score.ScoreInfo.Passed = false;
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
// could process an extra frame after the GameplayClock is stopped.
@ -950,6 +951,10 @@ namespace osu.Game.Screens.Play
{
screenSuspension?.Expire();
// if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
if (prepareScoreForDisplayTask == null)
Score.ScoreInfo.Passed = false;
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
// To resolve test failures, forcefully end playing synchronously when this screen exits.
// Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method.

View File

@ -12,6 +12,16 @@ namespace osu.Game.Screens.Play
{
public class SoloPlayer : SubmittingPlayer
{
public SoloPlayer()
: this(null)
{
}
protected SoloPlayer(PlayerConfiguration configuration = null)
: base(configuration)
{
}
protected override APIRequest<APIScoreToken> CreateTokenRequest()
{
if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId))
@ -27,9 +37,11 @@ namespace osu.Game.Screens.Play
protected override APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token)
{
Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null);
var beatmap = score.ScoreInfo.Beatmap;
int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value;
Debug.Assert(beatmap.OnlineBeatmapID != null);
int beatmapId = beatmap.OnlineBeatmapID.Value;
return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo);
}

View File

@ -27,6 +27,8 @@ namespace osu.Game.Screens.Play
[Resolved]
private IAPIProvider api { get; set; }
private TaskCompletionSource<bool> scoreSubmissionSource;
protected SubmittingPlayer(PlayerConfiguration configuration = null)
: base(configuration)
{
@ -106,27 +108,16 @@ namespace osu.Game.Screens.Play
{
await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false);
// token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure).
if (token == null)
return;
await submitScore(score).ConfigureAwait(false);
}
var tcs = new TaskCompletionSource<bool>();
var request = CreateSubmissionRequest(score, token.Value);
public override bool OnExiting(IScreen next)
{
var exiting = base.OnExiting(next);
request.Success += s =>
{
score.ScoreInfo.OnlineScoreID = s.ID;
tcs.SetResult(true);
};
submitScore(Score);
request.Failure += e =>
{
Logger.Error(e, "Failed to submit score");
tcs.SetResult(false);
};
api.Queue(request);
await tcs.Task.ConfigureAwait(false);
return exiting;
}
/// <summary>
@ -143,5 +134,33 @@ namespace osu.Game.Screens.Play
/// <param name="score">The score to be submitted.</param>
/// <param name="token">The submission token.</param>
protected abstract APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token);
private Task submitScore(Score score)
{
// token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure).
if (token == null)
return Task.CompletedTask;
if (scoreSubmissionSource != null)
return scoreSubmissionSource.Task;
scoreSubmissionSource = new TaskCompletionSource<bool>();
var request = CreateSubmissionRequest(score, token.Value);
request.Success += s =>
{
score.ScoreInfo.OnlineScoreID = s.ID;
scoreSubmissionSource.SetResult(true);
};
request.Failure += e =>
{
Logger.Error(e, "Failed to submit score");
scoreSubmissionSource.SetResult(false);
};
api.Queue(request);
return scoreSubmissionSource.Task;
}
}
}

View File

@ -4,9 +4,7 @@
using System.Globalization;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
@ -111,12 +109,9 @@ namespace osu.Game.Screens.Ranking.Expanded
var rating = Current.Value.DifficultyRating;
background.Colour = rating == DifficultyRating.ExpertPlus
? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959"))
: (ColourInfo)colours.ForDifficultyRating(rating);
background.Colour = colours.ForDifficultyRating(rating, true);
textFlow.Clear();
textFlow.AddText($"{wholePart}", s =>
{
s.Colour = Color4.Black;

View File

@ -95,8 +95,8 @@ namespace osu.Game.Skinning.Editor
// scale adjust applied to each individual item should match that of the quad itself.
var scaledDelta = new Vector2(
adjustedRect.Width / selectionRect.Width,
adjustedRect.Height / selectionRect.Height
MathF.Max(adjustedRect.Width / selectionRect.Width, 0),
MathF.Max(adjustedRect.Height / selectionRect.Height, 0)
);
foreach (var b in SelectedBlueprints)
@ -127,6 +127,7 @@ namespace osu.Game.Skinning.Editor
public override bool HandleFlip(Direction direction)
{
var selectionQuad = getSelectionQuad();
Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1);
foreach (var b in SelectedBlueprints)
{
@ -136,10 +137,8 @@ namespace osu.Game.Skinning.Editor
updateDrawablePosition(drawableItem, flippedPosition);
drawableItem.Scale *= new Vector2(
direction == Direction.Horizontal ? -1 : 1,
direction == Direction.Vertical ? -1 : 1
);
drawableItem.Scale *= scaleFactor;
drawableItem.Rotation -= drawableItem.Rotation % 180 * 2;
}
return true;

View File

@ -1,14 +1,18 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
@ -16,7 +20,7 @@ namespace osu.Game.Tests.Visual
/// <summary>
/// A player that exposes many components that would otherwise not be available, for testing purposes.
/// </summary>
public class TestPlayer : Player
public class TestPlayer : SoloPlayer
{
protected override bool PauseOnFocusLost { get; }
@ -35,6 +39,10 @@ namespace osu.Game.Tests.Visual
public new HealthProcessor HealthProcessor => base.HealthProcessor;
public bool TokenCreationRequested { get; private set; }
public Score SubmittedScore { get; private set; }
public new bool PauseCooldownActive => base.PauseCooldownActive;
public readonly List<JudgementResult> Results = new List<JudgementResult>();
@ -49,6 +57,20 @@ namespace osu.Game.Tests.Visual
PauseOnFocusLost = pauseOnFocusLost;
}
protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
protected override APIRequest<APIScoreToken> CreateTokenRequest()
{
TokenCreationRequested = true;
return base.CreateTokenRequest();
}
protected override APIRequest<MultiplayerScore> CreateSubmissionRequest(Score score, long token)
{
SubmittedScore = score;
return base.CreateSubmissionRequest(score, token);
}
protected override void PrepareReplay()
{
// Generally, replay generation is handled by whatever is constructing the player.

View File

@ -20,26 +20,26 @@
<ItemGroup Label="Package References">
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="DiffPlex" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.33" />
<PackageReference Include="Humanizer" Version="2.10.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.34" />
<PackageReference Include="Humanizer" Version="2.11.10" />
<PackageReference Include="MessagePack" Version="2.2.85" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.7" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.7" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.LocalisationAnalyser" Version="2021.608.0">
<PackageReference Include="ppy.LocalisationAnalyser" Version="2021.614.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.2.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.630.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.618.0" />
<PackageReference Include="Sentry" Version="3.4.0" />
<PackageReference Include="SharpCompress" Version="0.28.2" />
<PackageReference Include="Realm" Version="10.2.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.702.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.701.0" />
<PackageReference Include="Sentry" Version="3.6.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>

View File

@ -70,8 +70,8 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.630.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.618.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.702.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.701.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup>
@ -89,16 +89,16 @@
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies">
<PackageReference Include="DiffPlex" Version="1.6.3" />
<PackageReference Include="Humanizer" Version="2.10.1" />
<PackageReference Include="Humanizer" Version="2.11.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.630.0" />
<PackageReference Include="SharpCompress" Version="0.28.2" />
<PackageReference Include="ppy.osu.Framework" Version="2021.702.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2021.115.0" ExcludeAssets="all" />
<PackageReference Include="Realm" Version="10.2.0" />
<PackageReference Include="Realm" Version="10.2.1" />
</ItemGroup>
</Project>

View File

@ -308,6 +308,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GL/@EntryIndexedValue">GL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GLSL/@EntryIndexedValue">GLSL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HID/@EntryIndexedValue">HID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HSV/@EntryIndexedValue">HSV</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HUD/@EntryIndexedValue">HUD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>