1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-28 20:47:22 +08:00

Merge branch 'master' into catcher-trail-entry

This commit is contained in:
smoogipoo 2021-08-02 15:07:48 +09:00
commit 9b98014606
87 changed files with 1122 additions and 457 deletions

View File

@ -190,3 +190,5 @@ dotnet_diagnostic.CA2225.severity = none
# Banned APIs # Banned APIs
dotnet_diagnostic.RS0030.severity = error dotnet_diagnostic.RS0030.severity = error
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.722.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.730.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.728.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.728.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">

View File

@ -64,7 +64,7 @@
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
</Project> </Project>

View File

@ -130,7 +130,8 @@ namespace osu.Game.Rulesets.Catch
return new Mod[] return new Mod[]
{ {
new MultiMod(new ModWindUp(), new ModWindDown()), new MultiMod(new ModWindUp(), new ModWindDown()),
new CatchModFloatingFruits() new CatchModFloatingFruits(),
new CatchModMuted(),
}; };
default: default:

View File

@ -0,0 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModMuted : ModMuted<CatchHitObject>
{
}
}

View File

@ -253,7 +253,8 @@ namespace osu.Game.Rulesets.Mania
case ModType.Fun: case ModType.Fun:
return new Mod[] return new Mod[]
{ {
new MultiMod(new ModWindUp(), new ModWindDown()) new MultiMod(new ModWindUp(), new ModWindDown()),
new ManiaModMuted(),
}; };
default: default:

View File

@ -0,0 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModMuted : ModMuted<ManiaHitObject>
{
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModMuted : ModMuted<OsuHitObject>
{
}
}

View File

@ -4,8 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -16,7 +14,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -29,7 +26,6 @@ using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Osu.Utils; using osu.Game.Rulesets.Osu.Utils;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -67,11 +63,6 @@ namespace osu.Game.Rulesets.Osu.Mods
/// </summary> /// </summary>
private const float distance_cap = 380f; private const float distance_cap = 380f;
// The distances from the hit objects to the borders of the playfield they start to "turn around" and curve towards the middle.
// The closer the hit objects draw to the border, the sharper the turn
private const byte border_distance_x = 192;
private const byte border_distance_y = 144;
/// <summary> /// <summary>
/// The extent of rotation towards playfield centre when a circle is near the edge /// The extent of rotation towards playfield centre when a circle is near the edge
/// </summary> /// </summary>
@ -341,46 +332,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset) public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{ {
drawableRuleset.Overlays.Add(new TargetBeatContainer(drawableRuleset.Beatmap.HitObjects.First().StartTime)); drawableRuleset.Overlays.Add(new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime));
}
public class TargetBeatContainer : BeatSyncedContainer
{
private readonly double firstHitTime;
private PausableSkinnableSound sample;
public TargetBeatContainer(double firstHitTime)
{
this.firstHitTime = firstHitTime;
AllowMistimedEventFiring = false;
Divisor = 1;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana"))
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
if (!IsBeatSyncedWithTrack) return;
int timeSignature = (int)timingPoint.TimeSignature;
// play metronome from one measure before the first object.
if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature)
return;
sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f;
sample.Play();
}
} }
#endregion #endregion

View File

@ -189,6 +189,7 @@ namespace osu.Game.Rulesets.Osu
new OsuModTraceable(), new OsuModTraceable(),
new OsuModBarrelRoll(), new OsuModBarrelRoll(),
new OsuModApproachDifferent(), new OsuModApproachDifferent(),
new OsuModMuted(),
}; };
case ModType.System: case ModType.System:

View File

@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void onJudgementLoaded(DrawableOsuJudgement judgement) private void onJudgementLoaded(DrawableOsuJudgement judgement)
{ {
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent()); judgementAboveHitObjectLayer.Add(judgement.ProxiedAboveHitObjectsContent);
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
@ -150,6 +150,10 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject)); DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
judgementLayer.Add(explosion); judgementLayer.Add(explosion);
// the proxied content is added to judgementAboveHitObjectLayer once, on first load, and never removed from it.
// ensure that ordering is consistent with expectations (latest judgement should be front-most).
judgementAboveHitObjectLayer.ChangeChildDepth(explosion.ProxiedAboveHitObjectsContent, (float)-result.TimeAbsolute);
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);

View File

@ -0,0 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModMuted : ModMuted<TaikoHitObject>
{
}
}

View File

@ -149,7 +149,8 @@ namespace osu.Game.Rulesets.Taiko
case ModType.Fun: case ModType.Fun:
return new Mod[] return new Mod[]
{ {
new MultiMod(new ModWindUp(), new ModWindDown()) new MultiMod(new ModWindUp(), new ModWindDown()),
new TaikoModMuted(),
}; };
default: default:

View File

@ -0,0 +1,41 @@
// 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.Game.Extensions;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class TimeDisplayExtensionTest
{
private static readonly object[][] editor_formatted_duration_tests =
{
new object[] { new TimeSpan(0, 0, 0, 0, 50), "00:00:050" },
new object[] { new TimeSpan(0, 0, 0, 10, 50), "00:10:050" },
new object[] { new TimeSpan(0, 0, 5, 10), "05:10:000" },
new object[] { new TimeSpan(0, 1, 5, 10), "65:10:000" },
};
[TestCaseSource(nameof(editor_formatted_duration_tests))]
public void TestEditorFormat(TimeSpan input, string expectedOutput)
{
Assert.AreEqual(expectedOutput, input.ToEditorFormattedString());
}
private static readonly object[][] formatted_duration_tests =
{
new object[] { new TimeSpan(0, 0, 10), "00:10" },
new object[] { new TimeSpan(0, 5, 10), "05:10" },
new object[] { new TimeSpan(1, 5, 10), "01:05:10" },
new object[] { new TimeSpan(1, 1, 5, 10), "01:01:05:10" },
};
[TestCaseSource(nameof(formatted_duration_tests))]
public void TestFormattedDuration(TimeSpan input, string expectedOutput)
{
Assert.AreEqual(expectedOutput, input.ToFormattedDuration().ToString());
}
}
}

View File

@ -168,8 +168,8 @@ namespace osu.Game.Tests.Online
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
await AllowImport.Task; await AllowImport.Task.ConfigureAwait(false);
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)); return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
} }
} }

View File

@ -143,9 +143,9 @@ namespace osu.Game.Tests.Visual.SongSelect
public override async Task<StarDifficulty> GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default) public override async Task<StarDifficulty> GetDifficultyAsync(BeatmapInfo beatmapInfo, RulesetInfo rulesetInfo = null, IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default)
{ {
if (blockCalculation) if (blockCalculation)
await calculationBlocker.Task; await calculationBlocker.Task.ConfigureAwait(false);
return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken); return await base.GetDifficultyAsync(beatmapInfo, rulesetInfo, mods, cancellationToken).ConfigureAwait(false);
} }
} }
} }

View File

@ -1,16 +1,21 @@
// 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.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
{ {
public class TestSceneLabelledColourPalette : OsuTestScene public class TestSceneLabelledColourPalette : OsuManualInputManagerTestScene
{ {
private LabelledColourPalette component; private LabelledColourPalette component;
@ -30,21 +35,41 @@ namespace osu.Game.Tests.Visual.UserInterface
}, 8); }, 8);
} }
[Test]
public void TestUserInteractions()
{
createColourPalette();
assertColourCount(4);
clickAddColour();
assertColourCount(5);
deleteFirstColour();
assertColourCount(4);
clickFirstColour();
AddAssert("colour picker spawned", () => this.ChildrenOfType<OsuColourPicker>().Any());
}
private void createColourPalette(bool hasDescription = false) private void createColourPalette(bool hasDescription = false)
{ {
AddStep("create component", () => AddStep("create component", () =>
{ {
Child = new Container Child = new OsuContextMenuContainer
{ {
Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre, Child = new Container
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
ColourNamePrefix = "My colour #" Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ColourNamePrefix = "My colour #"
}
} }
}; };
@ -53,18 +78,49 @@ namespace osu.Game.Tests.Visual.UserInterface
component.Colours.AddRange(new[] component.Colours.AddRange(new[]
{ {
Color4.DarkRed, Colour4.DarkRed,
Color4.Aquamarine, Colour4.Aquamarine,
Color4.Goldenrod, Colour4.Goldenrod,
Color4.Gainsboro Colour4.Gainsboro
}); });
}); });
} }
private Color4 randomColour() => new Color4( private Colour4 randomColour() => new Color4(
RNG.NextSingle(), RNG.NextSingle(),
RNG.NextSingle(), RNG.NextSingle(),
RNG.NextSingle(), RNG.NextSingle(),
1); 1);
private void assertColourCount(int count) => AddAssert($"colour count is {count}", () => component.Colours.Count == count);
private void clickAddColour() => AddStep("click new colour button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ColourPalette.AddColourButton>().Single());
InputManager.Click(MouseButton.Left);
});
private void clickFirstColour() => AddStep("click first colour", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ColourDisplay>().First());
InputManager.Click(MouseButton.Left);
});
private void deleteFirstColour()
{
AddStep("right-click first colour", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<ColourDisplay>().First());
InputManager.Click(MouseButton.Right);
});
AddUntilStep("wait for menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
AddStep("click delete", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableOsuMenuItem>().Single());
InputManager.Click(MouseButton.Left);
});
}
} }
} }

View File

@ -1,21 +1,18 @@
// 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.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Tournament.Configuration;
using osu.Game.Tests; using osu.Game.Tests;
using osu.Game.Tournament.Configuration;
namespace osu.Game.Tournament.Tests.NonVisual namespace osu.Game.Tournament.Tests.NonVisual
{ {
[TestFixture] [TestFixture]
public class CustomTourneyDirectoryTest public class CustomTourneyDirectoryTest : TournamentHostTest
{ {
[Test] [Test]
public void TestDefaultDirectory() public void TestDefaultDirectory()
@ -24,7 +21,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
{ {
try try
{ {
var osu = loadOsu(host); var osu = LoadTournament(host);
var storage = osu.Dependencies.Get<Storage>(); var storage = osu.Dependencies.Get<Storage>();
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default"))); Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default")));
@ -54,7 +51,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
try try
{ {
var osu = loadOsu(host); var osu = LoadTournament(host);
storage = osu.Dependencies.Get<Storage>(); storage = osu.Dependencies.Get<Storage>();
@ -111,7 +108,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
try try
{ {
var osu = loadOsu(host); var osu = LoadTournament(host);
var storage = osu.Dependencies.Get<Storage>(); var storage = osu.Dependencies.Get<Storage>();
@ -151,25 +148,6 @@ namespace osu.Game.Tournament.Tests.NonVisual
} }
} }
private TournamentGameBase loadOsu(GameHost host)
{
var osu = new TournamentGameBase();
Task.Run(() => host.Run(osu))
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return osu;
}
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 90000)
{
Task task = Task.Run(() =>
{
while (!result()) Thread.Sleep(200);
});
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance); private string basePath(string testInstance) => Path.Combine(RuntimeInfo.StartupDirectory, "headless", testInstance);
} }
} }

View File

@ -0,0 +1,45 @@
// 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.IO;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Game.Rulesets;
using osu.Game.Tests;
namespace osu.Game.Tournament.Tests.NonVisual
{
public class DataLoadTest : TournamentHostTest
{
[Test]
public void TestUnavailableRuleset()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUnavailableRuleset)))
{
try
{
var osu = new TestTournament();
LoadTournament(host, osu);
var storage = osu.Dependencies.Get<Storage>();
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default")));
}
finally
{
host.Exit();
}
}
}
public class TestTournament : TournamentGameBase
{
[BackgroundDependencyLoader]
private void load()
{
Ruleset.Value = new RulesetInfo(); // not available
}
}
}
}

View File

@ -1,10 +1,7 @@
// 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.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -15,7 +12,7 @@ using osu.Game.Tournament.IPC;
namespace osu.Game.Tournament.Tests.NonVisual namespace osu.Game.Tournament.Tests.NonVisual
{ {
[TestFixture] [TestFixture]
public class IPCLocationTest public class IPCLocationTest : TournamentHostTest
{ {
[Test] [Test]
public void CheckIPCLocation() public void CheckIPCLocation()
@ -34,11 +31,11 @@ namespace osu.Game.Tournament.Tests.NonVisual
try try
{ {
var osu = loadOsu(host); var osu = LoadTournament(host);
TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get<Storage>(); TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get<Storage>();
FileBasedIPC ipc = null; FileBasedIPC ipc = null;
waitForOrAssert(() => (ipc = osu.Dependencies.Get<MatchIPCInfo>() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time"); WaitForOrAssert(() => (ipc = osu.Dependencies.Get<MatchIPCInfo>() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time");
Assert.True(ipc.SetIPCLocation(testStableInstallDirectory)); Assert.True(ipc.SetIPCLocation(testStableInstallDirectory));
Assert.True(storage.AllTournaments.Exists("stable.json")); Assert.True(storage.AllTournaments.Exists("stable.json"));
@ -51,24 +48,5 @@ namespace osu.Game.Tournament.Tests.NonVisual
} }
} }
} }
private TournamentGameBase loadOsu(GameHost host)
{
var osu = new TournamentGameBase();
Task.Run(() => host.Run(osu))
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return osu;
}
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 90000)
{
Task task = Task.Run(() =>
{
while (!result()) Thread.Sleep(200);
});
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
} }
} }

View File

@ -0,0 +1,33 @@
// 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.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Platform;
namespace osu.Game.Tournament.Tests.NonVisual
{
public abstract class TournamentHostTest
{
public static TournamentGameBase LoadTournament(GameHost host, TournamentGameBase tournament = null)
{
tournament ??= new TournamentGameBase();
Task.Run(() => host.Run(tournament))
.ContinueWith(t => Assert.Fail($"Host threw exception {t.Exception}"), TaskContinuationOptions.OnlyOnFaulted);
WaitForOrAssert(() => tournament.IsLoaded, @"osu! failed to start in a reasonable amount of time");
return tournament;
}
public static void WaitForOrAssert(Func<bool> result, string failureMessage, int timeout = 90000)
{
Task task = Task.Run(() =>
{
while (!result()) Thread.Sleep(200);
});
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -11,6 +10,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Extensions;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
@ -198,8 +198,8 @@ namespace osu.Game.Tournament.Components
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))), new DiffPiece(("Length", length.ToFormattedDuration().ToString())),
new DiffPiece(("BPM", $"{bpm:0.#}")) new DiffPiece(("BPM", $"{bpm:0.#}")),
} }
}, },
new Container new Container

View File

@ -27,6 +27,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
{ {
var teams = new List<TournamentTeam>(); var teams = new List<TournamentTeam>();
if (!storage.Exists(teams_filename))
return teams;
try try
{ {
using (Stream stream = storage.GetStream(teams_filename, FileAccess.Read, FileMode.Open)) using (Stream stream = storage.GetStream(teams_filename, FileAccess.Read, FileMode.Open))

View File

@ -9,11 +9,13 @@ using System.Threading.Tasks;
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.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Drawings.Components; using osu.Game.Tournament.Screens.Drawings.Components;
@ -51,6 +53,29 @@ namespace osu.Game.Tournament.Screens.Drawings
if (!TeamList.Teams.Any()) if (!TeamList.Teams.Any())
{ {
LinkFlowContainer links;
InternalChildren = new Drawable[]
{
new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = 0.3f,
},
new WarningBox("No drawings.txt file found. Please create one and restart the client."),
links = new LinkFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Y = 60,
AutoSizeAxes = Axes.Both
}
};
links.AddLink("Click for details on the file format", "https://osu.ppy.sh/wiki/en/Tournament_Drawings", t => t.Colour = Color4.White);
return; return;
} }

View File

@ -66,7 +66,9 @@ namespace osu.Game.Tournament
} }
ladder ??= new LadderInfo(); ladder ??= new LadderInfo();
ladder.Ruleset.Value ??= RulesetStore.AvailableRulesets.First();
ladder.Ruleset.Value = RulesetStore.GetRuleset(ladder.Ruleset.Value?.ShortName)
?? RulesetStore.AvailableRulesets.First();
bool addedInfo = false; bool addedInfo = false;

View File

@ -1,2 +1,3 @@
[*.cs] [*.cs]
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.

View File

@ -1,26 +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;
namespace osu.Game.Extensions
{
public static class EditorDisplayExtensions
{
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="milliseconds">A time value in milliseconds.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this double milliseconds) =>
ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="timeSpan">A time value.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
$"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{timeSpan:mm\\:ss\\:fff}";
}
}

View File

@ -0,0 +1,51 @@
// 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;
namespace osu.Game.Extensions
{
public static class TimeDisplayExtensions
{
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="milliseconds">A time value in milliseconds.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this double milliseconds) =>
ToEditorFormattedString(TimeSpan.FromMilliseconds(milliseconds));
/// <summary>
/// Get an editor formatted string (mm:ss:mss)
/// </summary>
/// <param name="timeSpan">A time value.</param>
/// <returns>An editor formatted display string.</returns>
public static string ToEditorFormattedString(this TimeSpan timeSpan) =>
$"{(timeSpan < TimeSpan.Zero ? "-" : string.Empty)}{(int)timeSpan.TotalMinutes:00}:{timeSpan:ss\\:fff}";
/// <summary>
/// Get a formatted duration (dd:hh:mm:ss with days/hours omitted if zero).
/// </summary>
/// <param name="milliseconds">A duration in milliseconds.</param>
/// <returns>A formatted duration string.</returns>
public static LocalisableString ToFormattedDuration(this double milliseconds) =>
ToFormattedDuration(TimeSpan.FromMilliseconds(milliseconds));
/// <summary>
/// Get a formatted duration (dd:hh:mm:ss with days/hours omitted if zero).
/// </summary>
/// <param name="timeSpan">A duration value.</param>
/// <returns>A formatted duration string.</returns>
public static LocalisableString ToFormattedDuration(this TimeSpan timeSpan)
{
if (timeSpan.TotalDays >= 1)
return new LocalisableFormattableString(timeSpan, @"dd\:hh\:mm\:ss");
if (timeSpan.TotalHours >= 1)
return new LocalisableFormattableString(timeSpan, @"hh\:mm\:ss");
return new LocalisableFormattableString(timeSpan, @"mm\:ss");
}
}
}

View File

@ -10,6 +10,7 @@ using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Users; using osu.Game.Users;
@ -25,6 +26,9 @@ namespace osu.Game.Graphics.Containers
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private OsuGame game { get; set; } private OsuGame game { get; set; }
[Resolved]
private GameHost host { get; set; }
public void AddLinks(string text, List<Link> links) public void AddLinks(string text, List<Link> links)
{ {
if (string.IsNullOrEmpty(text) || links == null) if (string.IsNullOrEmpty(text) || links == null)
@ -91,8 +95,11 @@ namespace osu.Game.Graphics.Containers
{ {
if (action != null) if (action != null)
action(); action();
else else if (game != null)
game?.HandleLink(link); game.HandleLink(link);
// fallback to handle cases where OsuGame is not available, ie. tournament client.
else if (link.Action == LinkAction.External)
host.OpenUrlExternally(link.Argument);
}, },
}); });
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -59,33 +60,37 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
InternalChild = new GridContainer InternalChildren = new Drawable[]
{ {
RelativeSizeAxes = Axes.X, new GridContainer
AutoSizeAxes = Axes.Y,
Content = new[]
{ {
new[] RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[]
{ {
handleContainer = new Container new[]
{ {
Anchor = Anchor.Centre, handleContainer = new Container
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5 },
Child = handle = new PlaylistItemHandle
{ {
Size = new Vector2(12), Anchor = Anchor.Centre,
Colour = HandleColour, Origin = Anchor.Centre,
AlwaysPresent = true, AutoSizeAxes = Axes.Both,
Alpha = 0 Padding = new MarginPadding { Horizontal = 5 },
} Child = handle = new PlaylistItemHandle
}, {
CreateContent() Size = new Vector2(12),
} Colour = HandleColour,
AlwaysPresent = true,
Alpha = 0
}
},
CreateContent()
}
},
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
}, },
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, new HoverClickSounds()
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
}; };
} }

View File

@ -20,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Size = TwoLayerButton.SIZE_EXTENDED; Size = TwoLayerButton.SIZE_EXTENDED;
Child = button = new TwoLayerButton Child = button = new TwoLayerButton(HoverSampleSet.Submit)
{ {
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,

View File

@ -56,6 +56,7 @@ namespace osu.Game.Graphics.UserInterface
private Vector2 hoverSpacing => new Vector2(3f, 0f); private Vector2 hoverSpacing => new Vector2(3f, 0f);
public DialogButton() public DialogButton()
: base(HoverSampleSet.Submit)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;

View File

@ -23,14 +23,20 @@ namespace osu.Game.Graphics.UserInterface
[Resolved] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
private readonly SpriteIcon linkIcon;
public ExternalLinkButton(string link = null) public ExternalLinkButton(string link = null)
{ {
Link = link; Link = link;
Size = new Vector2(12); Size = new Vector2(12);
InternalChild = new SpriteIcon InternalChildren = new Drawable[]
{ {
Icon = FontAwesome.Solid.ExternalLinkAlt, linkIcon = new SpriteIcon
RelativeSizeAxes = Axes.Both {
Icon = FontAwesome.Solid.ExternalLinkAlt,
RelativeSizeAxes = Axes.Both
},
new HoverClickSounds(HoverSampleSet.Submit)
}; };
} }
@ -42,13 +48,13 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
InternalChild.FadeColour(hoverColour, 500, Easing.OutQuint); linkIcon.FadeColour(hoverColour, 500, Easing.OutQuint);
return base.OnHover(e); return base.OnHover(e);
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
InternalChild.FadeColour(Color4.White, 500, Easing.OutQuint); linkIcon.FadeColour(Color4.White, 500, Easing.OutQuint);
base.OnHoverLost(e); base.OnHoverLost(e);
} }

View File

@ -10,8 +10,8 @@ namespace osu.Game.Graphics.UserInterface
[Description("default")] [Description("default")]
Default, Default,
[Description("soft")] [Description("submit")]
Soft, Submit,
[Description("button")] [Description("button")]
Button, Button,

View File

@ -71,7 +71,8 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
public TwoLayerButton() public TwoLayerButton(HoverSampleSet sampleSet = HoverSampleSet.Default)
: base(sampleSet)
{ {
Size = SIZE_RETRACTED; Size = SIZE_RETRACTED;
Shear = shear; Shear = shear;

View File

@ -1,32 +1,39 @@
// 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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
/// <summary> /// <summary>
/// A component which displays a colour along with related description text. /// A component which displays a colour along with related description text.
/// </summary> /// </summary>
public class ColourDisplay : CompositeDrawable, IHasCurrentValue<Color4> public class ColourDisplay : CompositeDrawable, IHasCurrentValue<Colour4>
{ {
private readonly BindableWithCurrent<Color4> current = new BindableWithCurrent<Color4>(); /// <summary>
/// Invoked when the user has requested the colour corresponding to this <see cref="ColourDisplay"/>
/// to be removed from its palette.
/// </summary>
public event Action<ColourDisplay> DeleteRequested;
private readonly BindableWithCurrent<Colour4> current = new BindableWithCurrent<Colour4>();
private Box fill;
private OsuSpriteText colourHexCode;
private OsuSpriteText colourName; private OsuSpriteText colourName;
public Bindable<Color4> Current public Bindable<Colour4> Current
{ {
get => current.Current; get => current.Current;
set => current.Current = value; set => current.Current = value;
@ -62,24 +69,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
Spacing = new Vector2(0, 10), Spacing = new Vector2(0, 10),
Children = new Drawable[] Children = new Drawable[]
{ {
new CircularContainer new ColourCircle
{ {
RelativeSizeAxes = Axes.X, Current = { BindTarget = Current },
Height = 100, DeleteRequested = () => DeleteRequested?.Invoke(this)
Masking = true,
Children = new Drawable[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both
},
colourHexCode = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Default.With(size: 12)
}
}
}, },
colourName = new OsuSpriteText colourName = new OsuSpriteText
{ {
@ -90,18 +83,64 @@ namespace osu.Game.Graphics.UserInterfaceV2
}; };
} }
protected override void LoadComplete() private class ColourCircle : OsuClickableContainer, IHasPopover, IHasContextMenu
{ {
base.LoadComplete(); public Bindable<Colour4> Current { get; } = new Bindable<Colour4>();
current.BindValueChanged(_ => updateColour(), true); public Action DeleteRequested { get; set; }
}
private void updateColour() private readonly Box fill;
{ private readonly OsuSpriteText colourHexCode;
fill.Colour = current.Value;
colourHexCode.Text = current.Value.ToHex(); public ColourCircle()
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(current.Value); {
RelativeSizeAxes = Axes.X;
Height = 100;
CornerRadius = 50;
Masking = true;
Action = this.ShowPopover;
Children = new Drawable[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both
},
colourHexCode = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.Default.With(size: 12)
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => updateColour(), true);
}
private void updateColour()
{
fill.Colour = Current.Value;
colourHexCode.Text = Current.Value.ToHex();
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(Current.Value);
}
public Popover GetPopover() => new OsuPopover(false)
{
Child = new OsuColourPicker
{
Current = { BindTarget = Current }
}
};
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke())
};
} }
} }
} }

View File

@ -1,14 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
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.Sprites;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
@ -17,7 +22,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// </summary> /// </summary>
public class ColourPalette : CompositeDrawable public class ColourPalette : CompositeDrawable
{ {
public BindableList<Color4> Colours { get; } = new BindableList<Color4>(); public BindableList<Colour4> Colours { get; } = new BindableList<Colour4>();
private string colourNamePrefix = "Colour"; private string colourNamePrefix = "Colour";
@ -36,36 +41,24 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
} }
private FillFlowContainer<ColourDisplay> palette; private FillFlowContainer palette;
private Container placeholder;
private IEnumerable<ColourDisplay> colourDisplays => palette.OfType<ColourDisplay>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
AutoSizeDuration = fade_duration;
AutoSizeEasing = Easing.OutQuint;
InternalChildren = new Drawable[] InternalChild = palette = new FillFlowContainer
{ {
palette = new FillFlowContainer<ColourDisplay> RelativeSizeAxes = Axes.X,
{ AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, Spacing = new Vector2(10),
AutoSizeAxes = Axes.Y, Direction = FillDirection.Full
Spacing = new Vector2(10),
Direction = FillDirection.Full
},
placeholder = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new OsuSpriteText
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Text = "(none)",
Font = OsuFont.Default.With(weight: FontWeight.Bold)
}
}
}; };
} }
@ -73,7 +66,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
base.LoadComplete(); base.LoadComplete();
Colours.BindCollectionChanged((_, __) => updatePalette(), true); Colours.BindCollectionChanged((_, args) =>
{
if (args.Action != NotifyCollectionChangedAction.Replace)
updatePalette();
}, true);
FinishTransforms(true); FinishTransforms(true);
} }
@ -83,37 +80,103 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
palette.Clear(); palette.Clear();
if (Colours.Any()) for (int i = 0; i < Colours.Count; ++i)
{ {
palette.FadeIn(fade_duration, Easing.OutQuint); // copy to avoid accesses to modified closure.
placeholder.FadeOut(fade_duration, Easing.OutQuint); int colourIndex = i;
} ColourDisplay display;
else
{ palette.Add(display = new ColourDisplay
palette.FadeOut(fade_duration, Easing.OutQuint); {
placeholder.FadeIn(fade_duration, Easing.OutQuint); Current = { Value = Colours[colourIndex] }
});
display.Current.BindValueChanged(colour => Colours[colourIndex] = colour.NewValue);
display.DeleteRequested += colourDeletionRequested;
} }
foreach (var item in Colours) palette.Add(new AddColourButton
{ {
palette.Add(new ColourDisplay Action = () => Colours.Add(Colour4.White)
{ });
Current = { Value = item }
});
}
reindexItems(); reindexItems();
} }
private void colourDeletionRequested(ColourDisplay display) => Colours.RemoveAt(palette.IndexOf(display));
private void reindexItems() private void reindexItems()
{ {
int index = 1; int index = 1;
foreach (var colour in palette) foreach (var colourDisplay in colourDisplays)
{ {
colour.ColourName = $"{colourNamePrefix} {index}"; colourDisplay.ColourName = $"{colourNamePrefix} {index}";
index += 1; index += 1;
} }
} }
internal class AddColourButton : CompositeDrawable
{
public Action Action
{
set => circularButton.Action = value;
}
private readonly OsuClickableContainer circularButton;
public AddColourButton()
{
AutoSizeAxes = Axes.Y;
Width = 100;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
circularButton = new OsuClickableContainer
{
RelativeSizeAxes = Axes.X,
Height = 100,
CornerRadius = 50,
Masking = true,
BorderThickness = 5,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.Transparent,
AlwaysPresent = true
},
new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(20),
Icon = FontAwesome.Solid.Plus
}
}
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "New"
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
circularButton.BorderColour = colours.BlueDarker;
}
}
} }
} }

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osuTK.Graphics; using osu.Framework.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
@ -13,7 +13,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
} }
public BindableList<Color4> Colours => Component.Colours; public BindableList<Colour4> Colours => Component.Colours;
public string ColourNamePrefix public string ColourNamePrefix
{ {

View File

@ -32,7 +32,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
Depth = 1 Depth = 1
}, },
new HoverClickSounds(HoverSampleSet.Soft) new HoverClickSounds()
}); });
} }

View File

@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
Depth = 1 Depth = 1
}, },
new HoverClickSounds(HoverSampleSet.Soft) new HoverClickSounds()
}); });
} }

View File

@ -150,7 +150,7 @@ namespace osu.Game.Online.API
userReq.Failure += ex => userReq.Failure += ex =>
{ {
if (ex.InnerException is WebException webException && webException.Message == @"Unauthorized") if (ex is WebException webException && webException.Message == @"Unauthorized")
{ {
log.Add(@"Login no longer valid"); log.Add(@"Login no longer valid");
Logout(); Logout();

View File

@ -86,8 +86,6 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
private APIRequestCompletionState completionState; private APIRequestCompletionState completionState;
private Action pendingFailure;
public void Perform(IAPIProvider api) public void Perform(IAPIProvider api)
{ {
if (!(api is APIAccess apiAccess)) if (!(api is APIAccess apiAccess))
@ -99,29 +97,23 @@ namespace osu.Game.Online.API
API = apiAccess; API = apiAccess;
User = apiAccess.LocalUser.Value; User = apiAccess.LocalUser.Value;
if (checkAndScheduleFailure()) if (isFailing) return;
return;
WebRequest = CreateWebRequest(); WebRequest = CreateWebRequest();
WebRequest.Failed += Fail; WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false; WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
if (checkAndScheduleFailure()) if (isFailing) return;
return;
if (!WebRequest.Aborted) // could have been aborted by a Cancel() call Logger.Log($@"Performing request {this}", LoggingTarget.Network);
{ WebRequest.Perform();
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
WebRequest.Perform();
}
if (checkAndScheduleFailure()) if (isFailing) return;
return;
PostProcess(); PostProcess();
API.Schedule(TriggerSuccess); TriggerSuccess();
} }
/// <summary> /// <summary>
@ -141,7 +133,10 @@ namespace osu.Game.Online.API
completionState = APIRequestCompletionState.Completed; completionState = APIRequestCompletionState.Completed;
} }
Success?.Invoke(); if (API == null)
Success?.Invoke();
else
API.Schedule(() => Success?.Invoke());
} }
internal void TriggerFailure(Exception e) internal void TriggerFailure(Exception e)
@ -154,7 +149,10 @@ namespace osu.Game.Online.API
completionState = APIRequestCompletionState.Failed; completionState = APIRequestCompletionState.Failed;
} }
Failure?.Invoke(e); if (API == null)
Failure?.Invoke(e);
else
API.Schedule(() => Failure?.Invoke(e));
} }
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
@ -163,59 +161,47 @@ namespace osu.Game.Online.API
{ {
lock (completionStateLock) lock (completionStateLock)
{ {
// while it doesn't matter if code following this check is run more than once,
// this avoids unnecessarily performing work where we are already sure the user has been informed.
if (completionState != APIRequestCompletionState.Waiting) if (completionState != APIRequestCompletionState.Waiting)
return; return;
}
WebRequest?.Abort(); WebRequest?.Abort();
// in the case of a cancellation we don't care about whether there's an error in the response. // in the case of a cancellation we don't care about whether there's an error in the response.
if (!(e is OperationCanceledException)) if (!(e is OperationCanceledException))
{
string responseString = WebRequest?.GetResponseString();
// naive check whether there's an error in the response to avoid unnecessary JSON deserialisation.
if (!string.IsNullOrEmpty(responseString) && responseString.Contains(@"""error"""))
{ {
try string responseString = WebRequest?.GetResponseString();
{
// attempt to decode a displayable error string. // naive check whether there's an error in the response to avoid unnecessary JSON deserialisation.
var error = JsonConvert.DeserializeObject<DisplayableError>(responseString); if (!string.IsNullOrEmpty(responseString) && responseString.Contains(@"""error"""))
if (error != null)
e = new APIException(error.ErrorMessage, e);
}
catch
{ {
try
{
// attempt to decode a displayable error string.
var error = JsonConvert.DeserializeObject<DisplayableError>(responseString);
if (error != null)
e = new APIException(error.ErrorMessage, e);
}
catch
{
}
} }
} }
}
Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network); Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network);
pendingFailure = () => TriggerFailure(e); TriggerFailure(e);
checkAndScheduleFailure(); }
} }
/// <summary> /// <summary>
/// Checked for cancellation or error. Also queues up the Failed event if we can. /// Whether this request is in a failing or failed state.
/// </summary> /// </summary>
/// <returns>Whether we are in a failed or cancelled state.</returns> private bool isFailing
private bool checkAndScheduleFailure()
{ {
lock (completionStateLock) get
{ {
if (pendingFailure == null) lock (completionStateLock)
return completionState == APIRequestCompletionState.Failed; return completionState == APIRequestCompletionState.Failed;
} }
if (API == null)
pendingFailure();
else
API.Schedule(pendingFailure);
pendingFailure = null;
return true;
} }
private class DisplayableError private class DisplayableError

View File

@ -31,6 +31,7 @@ namespace osu.Game.Online.Chat
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
public DrawableLinkCompiler(IEnumerable<Drawable> parts) public DrawableLinkCompiler(IEnumerable<Drawable> parts)
: base(HoverSampleSet.Submit)
{ {
Parts = parts.ToList(); Parts = parts.ToList();
} }

View File

@ -1058,7 +1058,7 @@ namespace osu.Game
OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode);
API.Activity.BindTo(newOsuScreen.Activity); API.Activity.BindTo(newOsuScreen.Activity);
MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; MusicController.AllowTrackAdjustments = newOsuScreen.AllowTrackAdjustments;
if (newOsuScreen.HideOverlaysOnEnter) if (newOsuScreen.HideOverlaysOnEnter)
CloseAllOverlays(); CloseAllOverlays();

View File

@ -479,7 +479,7 @@ namespace osu.Game
if (r.NewValue?.Available != true) if (r.NewValue?.Available != true)
{ {
// reject the change if the ruleset is not available. // reject the change if the ruleset is not available.
Ruleset.Value = r.OldValue; Ruleset.Value = r.OldValue?.Available == true ? r.OldValue : RulesetStore.AvailableRulesets.First();
return; return;
} }

View File

@ -50,6 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
protected Action ViewBeatmap; protected Action ViewBeatmap;
protected BeatmapPanel(BeatmapSetInfo setInfo) protected BeatmapPanel(BeatmapSetInfo setInfo)
: base(HoverSampleSet.Submit)
{ {
Debug.Assert(setInfo.OnlineBeatmapSetID != null); Debug.Assert(setInfo.OnlineBeatmapSetID != null);

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
@ -62,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet
} }
else else
{ {
length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToString(@"m\:ss"); length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration();
circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString(); circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString();
sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString();
} }

View File

@ -3,6 +3,9 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -13,6 +16,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -39,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Tabs
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private Sample sampleTabSwitched;
public ChannelTabItem(Channel value) public ChannelTabItem(Channel value)
: base(value) : base(value)
{ {
@ -112,6 +118,7 @@ namespace osu.Game.Overlays.Chat.Tabs
}, },
}, },
}, },
new HoverSounds()
}; };
} }
@ -152,11 +159,12 @@ namespace osu.Game.Overlays.Chat.Tabs
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, AudioManager audio)
{ {
BackgroundActive = colours.ChatBlue; BackgroundActive = colours.ChatBlue;
BackgroundInactive = colours.Gray4; BackgroundInactive = colours.Gray4;
backgroundHover = colours.Gray7; backgroundHover = colours.Gray7;
sampleTabSwitched = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
highlightBox.Colour = colours.Yellow; highlightBox.Colour = colours.Yellow;
} }
@ -217,7 +225,14 @@ namespace osu.Game.Overlays.Chat.Tabs
Text.Font = Text.Font.With(weight: FontWeight.Medium); Text.Font = Text.Font.With(weight: FontWeight.Medium);
} }
protected override void OnActivated() => updateState(); protected override void OnActivated()
{
if (IsLoaded)
sampleTabSwitched?.Play();
updateState();
}
protected override void OnDeactivated() => updateState(); protected override void OnDeactivated() => updateState();
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Chat.Tabs
if (value.Type != ChannelType.PM) if (value.Type != ChannelType.PM)
throw new ArgumentException("Argument value needs to have the targettype user!"); throw new ArgumentException("Argument value needs to have the targettype user!");
ClickableAvatar avatar; DrawableAvatar avatar;
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
@ -48,10 +48,9 @@ namespace osu.Game.Overlays.Chat.Tabs
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Masking = true, Masking = true,
Child = new DelayedLoadWrapper(avatar = new ClickableAvatar(value.Users.First()) Child = new DelayedLoadWrapper(avatar = new DrawableAvatar(value.Users.First())
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both
OpenOnClick = false,
}) })
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -400,35 +400,38 @@ namespace osu.Game.Overlays
NextTrack(); NextTrack();
} }
private bool allowRateAdjustments; private bool allowTrackAdjustments;
/// <summary> /// <summary>
/// Whether mod rate adjustments are allowed to be applied. /// Whether mod track adjustments are allowed to be applied.
/// </summary> /// </summary>
public bool AllowRateAdjustments public bool AllowTrackAdjustments
{ {
get => allowRateAdjustments; get => allowTrackAdjustments;
set set
{ {
if (allowRateAdjustments == value) if (allowTrackAdjustments == value)
return; return;
allowRateAdjustments = value; allowTrackAdjustments = value;
ResetTrackAdjustments(); ResetTrackAdjustments();
} }
} }
/// <summary> /// <summary>
/// Resets the speed adjustments currently applied on <see cref="CurrentTrack"/> and applies the mod adjustments if <see cref="AllowRateAdjustments"/> is <c>true</c>. /// Resets the adjustments currently applied on <see cref="CurrentTrack"/> and applies the mod adjustments if <see cref="AllowTrackAdjustments"/> is <c>true</c>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Does not reset speed adjustments applied directly to the beatmap track. /// Does not reset any adjustments applied directly to the beatmap track.
/// </remarks> /// </remarks>
public void ResetTrackAdjustments() public void ResetTrackAdjustments()
{ {
CurrentTrack.ResetSpeedAdjustments(); CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Balance);
CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Frequency);
CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Tempo);
CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Volume);
if (allowRateAdjustments) if (allowTrackAdjustments)
{ {
foreach (var mod in mods.Value.OfType<IApplicableToTrack>()) foreach (var mod in mods.Value.OfType<IApplicableToTrack>())
mod.ApplyToTrack(CurrentTrack); mod.ApplyToTrack(CurrentTrack);

View File

@ -14,6 +14,7 @@ using osu.Framework.Platform;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.News namespace osu.Game.Overlays.News
@ -28,6 +29,7 @@ namespace osu.Game.Overlays.News
private TextFlowContainer main; private TextFlowContainer main;
public NewsCard(APINewsPost post) public NewsCard(APINewsPost post)
: base(HoverSampleSet.Submit)
{ {
this.post = post; this.post = post;

View File

@ -15,13 +15,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.News.Sidebar namespace osu.Game.Overlays.News.Sidebar
{ {
public class MonthSection : CompositeDrawable public class MonthSection : CompositeDrawable
{ {
private const int animation_duration = 250; private const int animation_duration = 250;
private Sample sampleOpen;
private Sample sampleClose;
public readonly BindableBool Expanded = new BindableBool(); public readonly BindableBool Expanded = new BindableBool();
@ -51,6 +56,21 @@ namespace osu.Game.Overlays.News.Sidebar
} }
} }
}; };
Expanded.ValueChanged += expanded =>
{
if (expanded.NewValue)
sampleOpen?.Play();
else
sampleClose?.Play();
};
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
} }
private class DropdownHeader : OsuClickableContainer private class DropdownHeader : OsuClickableContainer
@ -59,6 +79,8 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly SpriteIcon icon; private readonly SpriteIcon icon;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
public DropdownHeader(int month, int year) public DropdownHeader(int month, int year)
{ {
var date = new DateTime(year, month, 1); var date = new DateTime(year, month, 1);
@ -104,6 +126,7 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly APINewsPost post; private readonly APINewsPost post;
public PostButton(APINewsPost post) public PostButton(APINewsPost post)
: base(HoverSampleSet.Submit)
{ {
this.post = post; this.post = post;

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Profile.Sections namespace osu.Game.Overlays.Profile.Sections
{ {
@ -17,6 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
protected BeatmapMetadataContainer(BeatmapInfo beatmap) protected BeatmapMetadataContainer(BeatmapInfo beatmap)
: base(HoverSampleSet.Submit)
{ {
this.beatmap = beatmap; this.beatmap = beatmap;

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
@ -52,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
new OsuSpriteText new OsuSpriteText
{ {
Font = OsuFont.GetFont(size: 12), Font = OsuFont.GetFont(size: 12),
Text = UsersStrings.ShowExtraTopRanksPpWeight(weight.ToString("0%")) Text = UsersStrings.ShowExtraTopRanksPpWeight(weight.ToLocalisableString("0%"))
} }
} }
}; };

View File

@ -5,6 +5,9 @@ using osu.Framework.Graphics;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;
using System;
namespace osu.Game.Overlays.Rankings namespace osu.Game.Overlays.Rankings
{ {
@ -29,13 +32,14 @@ namespace osu.Game.Overlays.Rankings
{ {
public RankingsTitle() public RankingsTitle()
{ {
Title = "ranking"; Title = PageTitleStrings.MainRankingControllerDefault;
Description = "find out who's the best right now"; Description = "find out who's the best right now";
IconTexture = "Icons/Hexacons/rankings"; IconTexture = "Icons/Hexacons/rankings";
} }
} }
} }
[LocalisableEnum(typeof(RankingsScopeEnumLocalisationMapper))]
public enum RankingsScope public enum RankingsScope
{ {
Performance, Performance,
@ -43,4 +47,28 @@ namespace osu.Game.Overlays.Rankings
Score, Score,
Country Country
} }
public class RankingsScopeEnumLocalisationMapper : EnumLocalisationMapper<RankingsScope>
{
public override LocalisableString Map(RankingsScope value)
{
switch (value)
{
case RankingsScope.Performance:
return RankingsStrings.TypePerformance;
case RankingsScope.Spotlights:
return RankingsStrings.TypeCharts;
case RankingsScope.Score:
return RankingsStrings.TypeScore;
case RankingsScope.Country:
return RankingsStrings.TypeCountry;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
} }

View File

@ -1,19 +1,43 @@
// 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 osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings namespace osu.Game.Overlays.Rankings
{ {
public class RankingsSortTabControl : OverlaySortTabControl<RankingsSortCriteria> public class RankingsSortTabControl : OverlaySortTabControl<RankingsSortCriteria>
{ {
public RankingsSortTabControl() public RankingsSortTabControl()
{ {
Title = "Show"; Title = RankingsStrings.FilterTitle.ToUpper();
} }
} }
[LocalisableEnum(typeof(RankingsSortCriteriaEnumLocalisationMapper))]
public enum RankingsSortCriteria public enum RankingsSortCriteria
{ {
All, All,
Friends Friends
} }
public class RankingsSortCriteriaEnumLocalisationMapper : EnumLocalisationMapper<RankingsSortCriteria>
{
public override LocalisableString Map(RankingsSortCriteria value)
{
switch (value)
{
case RankingsSortCriteria.All:
return SortStrings.All;
case RankingsSortCriteria.Friends:
return SortStrings.Friends;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
}
}
} }

View File

@ -16,6 +16,8 @@ using System.Collections.Generic;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings namespace osu.Game.Overlays.Rankings
{ {
@ -92,10 +94,10 @@ namespace osu.Game.Overlays.Rankings
Margin = new MarginPadding { Bottom = 5 }, Margin = new MarginPadding { Bottom = 5 },
Children = new Drawable[] Children = new Drawable[]
{ {
startDateColumn = new InfoColumn(@"Start Date"), startDateColumn = new InfoColumn(RankingsStrings.SpotlightStartDate),
endDateColumn = new InfoColumn(@"End Date"), endDateColumn = new InfoColumn(RankingsStrings.SpotlightEndDate),
mapCountColumn = new InfoColumn(@"Map Count"), mapCountColumn = new InfoColumn(RankingsStrings.SpotlightMapCount),
participantsColumn = new InfoColumn(@"Participants") participantsColumn = new InfoColumn(RankingsStrings.SpotlightParticipants)
} }
}, },
new RankingsSortTabControl new RankingsSortTabControl
@ -122,22 +124,22 @@ namespace osu.Game.Overlays.Rankings
{ {
startDateColumn.Value = dateToString(response.Spotlight.StartDate); startDateColumn.Value = dateToString(response.Spotlight.StartDate);
endDateColumn.Value = dateToString(response.Spotlight.EndDate); endDateColumn.Value = dateToString(response.Spotlight.EndDate);
mapCountColumn.Value = response.BeatmapSets.Count.ToString(); mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"N0");
participantsColumn.Value = response.Spotlight.Participants?.ToString("N0"); participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0");
} }
private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); private LocalisableString dateToString(DateTimeOffset date) => date.ToLocalisableString(@"yyyy-MM-dd");
private class InfoColumn : FillFlowContainer private class InfoColumn : FillFlowContainer
{ {
public string Value public LocalisableString Value
{ {
set => valueText.Text = value; set => valueText.Text = value;
} }
private readonly OsuSpriteText valueText; private readonly OsuSpriteText valueText;
public InfoColumn(string name) public InfoColumn(LocalisableString name)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Direction = FillDirection.Vertical; Direction = FillDirection.Vertical;

View File

@ -9,6 +9,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
{ {
@ -19,14 +21,14 @@ namespace osu.Game.Overlays.Rankings.Tables
{ {
} }
protected override TableColumn[] CreateAdditionalHeaders() => new[] protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
{ {
new TableColumn("Active Users", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatActiveUsers, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatRankedScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatAverageScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatPerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true),
new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatAveragePerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}; };
protected override Country GetCountry(CountryStatistics item) => item.Country; protected override Country GetCountry(CountryStatistics item) => item.Country;
@ -35,29 +37,29 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[] protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[]
{ {
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.ActiveUsers:N0}", Text = item.ActiveUsers.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.PlayCount:N0}", Text = item.PlayCount.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.RankedScore:N0}", Text = item.RankedScore.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.RankedScore / Math.Max(item.ActiveUsers, 1):N0}", Text = (item.RankedScore / Math.Max(item.ActiveUsers, 1)).ToLocalisableString(@"N0")
}, },
new RowText new RowText
{ {
Text = $@"{item.Performance:N0}", Text = item.Performance.ToLocalisableString(@"N0")
}, },
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.Performance / Math.Max(item.ActiveUsers, 1):N0}", Text = (item.Performance / Math.Max(item.ActiveUsers, 1)).ToLocalisableString(@"N0")
} }
}; };

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
@ -15,14 +17,14 @@ namespace osu.Game.Overlays.Rankings.Tables
{ {
} }
protected override TableColumn[] CreateUniqueHeaders() => new[] protected override RankingsTableColumn[] CreateUniqueHeaders() => new[]
{ {
new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatPerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true),
}; };
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{ {
new RowText { Text = $@"{item.PP:N0}", } new RowText { Text = item.PP.ToLocalisableString(@"N0"), }
}; };
} }
} }

View File

@ -55,29 +55,24 @@ namespace osu.Game.Overlays.Rankings.Tables
rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground { Height = row_height })); rankings.ForEach(_ => backgroundFlow.Add(new TableRowBackground { Height = row_height }));
Columns = mainHeaders.Concat(CreateAdditionalHeaders()).ToArray(); Columns = mainHeaders.Concat(CreateAdditionalHeaders()).Cast<TableColumn>().ToArray();
Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular(); Content = rankings.Select((s, i) => createContent((page - 1) * items_per_page + i, s)).ToArray().ToRectangular();
} }
private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray(); private Drawable[] createContent(int index, TModel item) => new Drawable[] { createIndexDrawable(index), createMainContent(item) }.Concat(CreateAdditionalContent(item)).ToArray();
private static TableColumn[] mainHeaders => new[] private static RankingsTableColumn[] mainHeaders => new[]
{ {
new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 40)), // place new RankingsTableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 40)), // place
new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension()), // flag and username (country name) new RankingsTableColumn(string.Empty, Anchor.CentreLeft, new Dimension()), // flag and username (country name)
}; };
protected abstract TableColumn[] CreateAdditionalHeaders(); protected abstract RankingsTableColumn[] CreateAdditionalHeaders();
protected abstract Drawable[] CreateAdditionalContent(TModel item); protected abstract Drawable[] CreateAdditionalContent(TModel item);
protected virtual string HighlightedColumn => @"Performance"; protected sealed override Drawable CreateHeader(int index, TableColumn column)
=> (column as RankingsTableColumn)?.CreateHeaderText() ?? new HeaderText(column?.Header ?? default, false);
protected override Drawable CreateHeader(int index, TableColumn column)
{
var title = column?.Header ?? default;
return new HeaderText(title, title == HighlightedColumn);
}
protected abstract Country GetCountry(TModel item); protected abstract Country GetCountry(TModel item);
@ -85,7 +80,7 @@ namespace osu.Game.Overlays.Rankings.Tables
private OsuSpriteText createIndexDrawable(int index) => new RowText private OsuSpriteText createIndexDrawable(int index) => new RowText
{ {
Text = $"#{index + 1}", Text = (index + 1).ToLocalisableString(@"\##"),
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.SemiBold) Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.SemiBold)
}; };
@ -106,6 +101,19 @@ namespace osu.Game.Overlays.Rankings.Tables
} }
}; };
protected class RankingsTableColumn : TableColumn
{
protected readonly bool Highlighted;
public RankingsTableColumn(LocalisableString? header = null, Anchor anchor = Anchor.TopLeft, Dimension dimension = null, bool highlighted = false)
: base(header, anchor, dimension)
{
Highlighted = highlighted;
}
public virtual HeaderText CreateHeaderText() => new HeaderText(Header, Highlighted);
}
protected class HeaderText : OsuSpriteText protected class HeaderText : OsuSpriteText
{ {
private readonly bool isHighlighted; private readonly bool isHighlighted;
@ -136,7 +144,7 @@ namespace osu.Game.Overlays.Rankings.Tables
} }
} }
protected class ColoredRowText : RowText protected class ColouredRowText : RowText
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)

View File

@ -4,6 +4,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
@ -15,24 +17,22 @@ namespace osu.Game.Overlays.Rankings.Tables
{ {
} }
protected override TableColumn[] CreateUniqueHeaders() => new[] protected override RankingsTableColumn[] CreateUniqueHeaders() => new[]
{ {
new TableColumn("Total Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatTotalScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)) new RankingsTableColumn(RankingsStrings.StatRankedScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true)
}; };
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[] protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{ {
new ColoredRowText new ColouredRowText
{ {
Text = $@"{item.TotalScore:N0}", Text = item.TotalScore.ToLocalisableString(@"N0"),
}, },
new RowText new RowText
{ {
Text = $@"{item.RankedScore:N0}", Text = item.RankedScore.ToLocalisableString(@"N0")
} }
}; };
protected override string HighlightedColumn => @"Ranked Score";
} }
} }

View File

@ -10,6 +10,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings.Tables namespace osu.Game.Overlays.Rankings.Tables
{ {
@ -20,22 +21,16 @@ namespace osu.Game.Overlays.Rankings.Tables
{ {
} }
protected virtual IEnumerable<string> GradeColumns => new List<string> { "SS", "S", "A" }; protected virtual IEnumerable<LocalisableString> GradeColumns => new List<LocalisableString> { RankingsStrings.Statss, RankingsStrings.Stats, RankingsStrings.Stata };
protected override TableColumn[] CreateAdditionalHeaders() => new[] protected override RankingsTableColumn[] CreateAdditionalHeaders() => new[]
{ {
new TableColumn("Accuracy", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatAccuracy, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}.Concat(CreateUniqueHeaders()) }.Concat(CreateUniqueHeaders())
.Concat(GradeColumns.Select(grade => new TableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)))) .Concat(GradeColumns.Select(grade => new GradeTableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize))))
.ToArray(); .ToArray();
protected override Drawable CreateHeader(int index, TableColumn column)
{
var title = column?.Header ?? default;
return new UserTableHeaderText(title, HighlightedColumn == title, GradeColumns.Contains(title.ToString()));
}
protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; protected sealed override Country GetCountry(UserStatistics item) => item.User.Country;
protected sealed override Drawable CreateFlagContent(UserStatistics item) protected sealed override Drawable CreateFlagContent(UserStatistics item)
@ -52,28 +47,38 @@ namespace osu.Game.Overlays.Rankings.Tables
protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[] protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[]
{ {
new ColoredRowText { Text = item.DisplayAccuracy, }, new ColouredRowText { Text = item.DisplayAccuracy, },
new ColoredRowText { Text = $@"{item.PlayCount:N0}", }, new ColouredRowText { Text = item.PlayCount.ToLocalisableString(@"N0") },
}.Concat(CreateUniqueContent(item)).Concat(new[] }.Concat(CreateUniqueContent(item)).Concat(new[]
{ {
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]:N0}", }, new ColouredRowText { Text = (item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]).ToLocalisableString(@"N0"), },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]:N0}", }, new ColouredRowText { Text = (item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]).ToLocalisableString(@"N0"), },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.A]:N0}", } new ColouredRowText { Text = item.GradesCount[ScoreRank.A].ToLocalisableString(@"N0"), }
}).ToArray(); }).ToArray();
protected abstract TableColumn[] CreateUniqueHeaders(); protected abstract RankingsTableColumn[] CreateUniqueHeaders();
protected abstract Drawable[] CreateUniqueContent(UserStatistics item); protected abstract Drawable[] CreateUniqueContent(UserStatistics item);
private class UserTableHeaderText : HeaderText private class GradeTableColumn : RankingsTableColumn
{ {
public UserTableHeaderText(LocalisableString text, bool isHighlighted, bool isGrade) public GradeTableColumn(LocalisableString? header = null, Anchor anchor = Anchor.TopLeft, Dimension dimension = null, bool highlighted = false)
: base(header, anchor, dimension, highlighted)
{
}
public override HeaderText CreateHeaderText() => new GradeHeaderText(Header, Highlighted);
}
private class GradeHeaderText : HeaderText
{
public GradeHeaderText(LocalisableString text, bool isHighlighted)
: base(text, isHighlighted) : base(text, isHighlighted)
{ {
Margin = new MarginPadding Margin = new MarginPadding
{ {
// Grade columns have extra horizontal padding for readibility // Grade columns have extra horizontal padding for readibility
Horizontal = isGrade ? 20 : 10, Horizontal = 20,
Vertical = 5 Vertical = 5
}; };
} }

View File

@ -138,7 +138,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, },
} }
} }
} },
new HoverClickSounds()
}; };
foreach (var b in bindings) foreach (var b in bindings)
@ -458,6 +459,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = keyBinding.KeyCombination.ReadableString(), Text = keyBinding.KeyCombination.ReadableString(),
}, },
new HoverSounds()
}; };
} }

View File

@ -1,6 +1,7 @@
// 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.Diagnostics; using System.Diagnostics;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Judgements
private readonly Container aboveHitObjectsContent; private readonly Container aboveHitObjectsContent;
private readonly Lazy<Drawable> proxiedAboveHitObjectsContent;
public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value;
/// <summary> /// <summary>
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>. /// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
/// </summary> /// </summary>
@ -52,6 +56,8 @@ namespace osu.Game.Rulesets.Judgements
Depth = float.MinValue, Depth = float.MinValue,
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}); });
proxiedAboveHitObjectsContent = new Lazy<Drawable>(() => aboveHitObjectsContent.CreateProxy());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -60,8 +66,6 @@ namespace osu.Game.Rulesets.Judgements
prepareDrawables(); prepareDrawables();
} }
public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy();
/// <summary> /// <summary>
/// Apply top-level animations to the current judgement when successfully hit. /// Apply top-level animations to the current judgement when successfully hit.
/// If displaying components which require lifetime extensions, manually adjusting <see cref="Drawable.LifetimeEnd"/> is required. /// If displaying components which require lifetime extensions, manually adjusting <see cref="Drawable.LifetimeEnd"/> is required.

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToHealthProcessor : IApplicableMod public interface IApplicableToHealthProcessor : IApplicableMod
{ {
/// <summary> /// <summary>
/// Provide a <see cref="HealthProcessor"/> to a mod. Called once on initialisation of a play instance. /// Provides a loaded <see cref="HealthProcessor"/> to a mod. Called once on initialisation of a play instance.
/// </summary> /// </summary>
void ApplyToHealthProcessor(HealthProcessor healthProcessor); void ApplyToHealthProcessor(HealthProcessor healthProcessor);
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToScoreProcessor : IApplicableMod public interface IApplicableToScoreProcessor : IApplicableMod
{ {
/// <summary> /// <summary>
/// Provide a <see cref="ScoreProcessor"/> to a mod. Called once on initialisation of a play instance. /// Provides a loaded <see cref="ScoreProcessor"/> to a mod. Called once on initialisation of a play instance.
/// </summary> /// </summary>
void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); void ApplyToScoreProcessor(ScoreProcessor scoreProcessor);

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.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mods
{
public class Metronome : BeatSyncedContainer, IAdjustableAudioComponent
{
private readonly double firstHitTime;
private readonly PausableSkinnableSound sample;
/// <param name="firstHitTime">Start time of the first hit object, used for providing a count down.</param>
public Metronome(double firstHitTime)
{
this.firstHitTime = firstHitTime;
AllowMistimedEventFiring = false;
Divisor = 1;
InternalChild = sample = new PausableSkinnableSound(new SampleInfo("Gameplay/catch-banana"));
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
if (!IsBeatSyncedWithTrack) return;
int timeSignature = (int)timingPoint.TimeSignature;
// play metronome from one measure before the first object.
if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature)
return;
sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f;
sample.Play();
}
#region IAdjustableAudioComponent
public IBindable<double> AggregateVolume => sample.AggregateVolume;
public IBindable<double> AggregateBalance => sample.AggregateBalance;
public IBindable<double> AggregateFrequency => sample.AggregateFrequency;
public IBindable<double> AggregateTempo => sample.AggregateTempo;
public BindableNumber<double> Volume => sample.Volume;
public BindableNumber<double> Balance => sample.Balance;
public BindableNumber<double> Frequency => sample.Frequency;
public BindableNumber<double> Tempo => sample.Tempo;
public void BindAdjustments(IAggregateAudioAdjustment component)
{
sample.BindAdjustments(component);
}
public void UnbindAdjustments(IAggregateAudioAdjustment component)
{
sample.UnbindAdjustments(component);
}
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable)
{
sample.AddAdjustment(type, adjustBindable);
}
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable)
{
sample.RemoveAdjustment(type, adjustBindable);
}
public void RemoveAllAdjustments(AdjustableProperty type)
{
sample.RemoveAllAdjustments(type);
}
#endregion
}
}

View File

@ -0,0 +1,104 @@
// 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 osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModMuted : Mod
{
public override string Name => "Muted";
public override string Acronym => "MU";
public override IconUsage? Icon => FontAwesome.Solid.VolumeMute;
public override string Description => "Can you still feel the rhythm without music?";
public override ModType Type => ModType.Fun;
public override double ScoreMultiplier => 1;
}
public abstract class ModMuted<TObject> : ModMuted, IApplicableToDrawableRuleset<TObject>, IApplicableToTrack, IApplicableToScoreProcessor
where TObject : HitObject
{
private readonly BindableNumber<double> mainVolumeAdjust = new BindableDouble(0.5);
private readonly BindableNumber<double> metronomeVolumeAdjust = new BindableDouble(0.5);
private BindableNumber<int> currentCombo;
[SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")]
public BindableBool EnableMetronome { get; } = new BindableBool
{
Default = true,
Value = true
};
[SettingSource("Final volume at combo", "The combo count at which point the track reaches its final volume.")]
public BindableInt MuteComboCount { get; } = new BindableInt
{
Default = 100,
Value = 100,
MinValue = 0,
MaxValue = 500,
};
[SettingSource("Start muted", "Increase volume as combo builds.")]
public BindableBool InverseMuting { get; } = new BindableBool
{
Default = false,
Value = false
};
[SettingSource("Mute hit sounds", "Hit sounds are also muted alongside the track.")]
public BindableBool AffectsHitSounds { get; } = new BindableBool
{
Default = true,
Value = true
};
public void ApplyToTrack(ITrack track)
{
track.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust);
}
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
{
if (EnableMetronome.Value)
{
Metronome metronome;
drawableRuleset.Overlays.Add(metronome = new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime));
metronome.AddAdjustment(AdjustableProperty.Volume, metronomeVolumeAdjust);
}
if (AffectsHitSounds.Value)
drawableRuleset.Audio.AddAdjustment(AdjustableProperty.Volume, mainVolumeAdjust);
}
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
{
currentCombo = scoreProcessor.Combo.GetBoundCopy();
currentCombo.BindValueChanged(combo =>
{
double dimFactor = Math.Min(1, (double)combo.NewValue / MuteComboCount.Value);
if (InverseMuting.Value)
dimFactor = 1 - dimFactor;
scoreProcessor.TransformBindableTo(metronomeVolumeAdjust, dimFactor, 500, Easing.OutQuint);
scoreProcessor.TransformBindableTo(mainVolumeAdjust, 1 - dimFactor, 500, Easing.OutQuint);
}, true);
}
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
}
}

View File

@ -1,29 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -98,6 +99,14 @@ namespace osu.Game.Rulesets.UI
private DrawableRulesetDependencies dependencies; private DrawableRulesetDependencies dependencies;
/// <summary>
/// Audio adjustments which are applied to the playfield.
/// </summary>
/// <remarks>
/// Does not affect <see cref="Overlays"/>.
/// </remarks>
public IAdjustableAudioComponent Audio { get; private set; }
/// <summary> /// <summary>
/// Creates a ruleset visualisation for the provided ruleset and beatmap. /// Creates a ruleset visualisation for the provided ruleset and beatmap.
/// </summary> /// </summary>
@ -155,23 +164,28 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(CancellationToken? cancellationToken) private void load(CancellationToken? cancellationToken)
{ {
InternalChildren = new Drawable[] AudioContainer audioContainer;
InternalChild = frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime)
{ {
frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) FrameStablePlayback = FrameStablePlayback,
Children = new Drawable[]
{ {
FrameStablePlayback = FrameStablePlayback, FrameStableComponents,
Children = new Drawable[] audioContainer = new AudioContainer
{ {
FrameStableComponents, RelativeSizeAxes = Axes.Both,
KeyBindingInputManager Child = KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield) .WithChild(Playfield)
), ),
Overlays, },
} Overlays,
}, }
}; };
Audio = audioContainer;
if ((ResumeOverlay = CreateResumeOverlay()) != null) if ((ResumeOverlay = CreateResumeOverlay()) != null)
{ {
AddInternal(CreateInputManager() AddInternal(CreateInputManager()

View File

@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit
public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool DisallowExternalBeatmapRulesetChanges => true;
public override bool AllowRateAdjustments => false; public override bool AllowTrackAdjustments => false;
protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash;

View File

@ -11,7 +11,6 @@ using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osuTK.Graphics; using osuTK.Graphics;
@ -67,7 +66,6 @@ namespace osu.Game.Screens.Edit
private EditorClock clock { get; set; } private EditorClock clock { get; set; }
public RowBackground(object item) public RowBackground(object item)
: base(HoverSampleSet.Soft)
{ {
Item = item; Item = item;

View File

@ -2,6 +2,7 @@
// 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.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;
@ -32,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup
var colours = Beatmap.BeatmapSkin?.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value; var colours = Beatmap.BeatmapSkin?.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value;
if (colours != null) if (colours != null)
comboColours.Colours.AddRange(colours); comboColours.Colours.AddRange(colours.Select(c => (Colour4)c));
} }
} }
} }

View File

@ -59,9 +59,9 @@ namespace osu.Game.Screens
Bindable<RulesetInfo> Ruleset { get; } Bindable<RulesetInfo> Ruleset { get; }
/// <summary> /// <summary>
/// Whether mod rate adjustments are allowed to be applied. /// Whether mod track adjustments are allowed to be applied.
/// </summary> /// </summary>
bool AllowRateAdjustments { get; } bool AllowTrackAdjustments { get; }
/// <summary> /// <summary>
/// Invoked when the back button has been pressed to close any overlays before exiting this <see cref="IOsuScreen"/>. /// Invoked when the back button has been pressed to close any overlays before exiting this <see cref="IOsuScreen"/>.

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Menu
public override bool AllowExternalScreenChange => true; public override bool AllowExternalScreenChange => true;
public override bool AllowRateAdjustments => false; public override bool AllowTrackAdjustments => false;
private Screen songSelect; private Screen songSelect;

View File

@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool DisallowExternalBeatmapRulesetChanges => true;
// We are managing our own adjustments. For now, this happens inside the Player instances themselves. // We are managing our own adjustments. For now, this happens inside the Player instances themselves.
public override bool AllowRateAdjustments => false; public override bool AllowTrackAdjustments => false;
/// <summary> /// <summary>
/// Whether all spectating players have finished loading. /// Whether all spectating players have finished loading.

View File

@ -81,7 +81,7 @@ namespace osu.Game.Screens
public virtual float BackgroundParallaxAmount => 1; public virtual float BackgroundParallaxAmount => 1;
public virtual bool AllowRateAdjustments => true; public virtual bool AllowTrackAdjustments => true;
public Bindable<WorkingBeatmap> Beatmap { get; private set; } public Bindable<WorkingBeatmap> Beatmap { get; private set; }

View File

@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
// We are managing our own adjustments (see OnEntering/OnExiting). // We are managing our own adjustments (see OnEntering/OnExiting).
public override bool AllowRateAdjustments => false; public override bool AllowTrackAdjustments => false;
private readonly IBindable<bool> gameActive = new Bindable<bool>(true); private readonly IBindable<bool> gameActive = new Bindable<bool>(true);
@ -297,11 +297,19 @@ namespace osu.Game.Screens.Play
ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged); ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged);
HealthProcessor.Failed += onFail; HealthProcessor.Failed += onFail;
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>()) // Provide judgement processors to mods after they're loaded so that they're on the gameplay clock,
mod.ApplyToScoreProcessor(ScoreProcessor); // this is required for mods that apply transforms to these processors.
ScoreProcessor.OnLoadComplete += _ =>
{
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
};
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>()) HealthProcessor.OnLoadComplete += _ =>
mod.ApplyToHealthProcessor(HealthProcessor); {
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>())
mod.ApplyToHealthProcessor(HealthProcessor);
};
IsBreakTime.BindTo(breakTracker.IsBreakTime); IsBreakTime.BindTo(breakTracker.IsBreakTime);
IsBreakTime.BindValueChanged(onBreakTimeChanged, true); IsBreakTime.BindValueChanged(onBreakTimeChanged, true);

View File

@ -178,7 +178,7 @@ namespace osu.Game.Screens.Play
float barHeight = bottom_bar_height + handle_size.Y; float barHeight = bottom_bar_height + handle_size.Y;
bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In); bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In);
graph.MoveToY(ShowGraph.Value ? 0 : bottom_bar_height + graph_height, transition_duration, Easing.In); graph.FadeTo(ShowGraph.Value ? 1 : 0, transition_duration, Easing.In);
updateInfoMargin(); updateInfoMargin();
} }

View File

@ -24,6 +24,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -333,7 +334,7 @@ namespace osu.Game.Screens.Select
{ {
Name = "Length", Name = "Length",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"), Content = beatmap.BeatmapInfo.Length.ToFormattedDuration().ToString(),
}), }),
bpmLabelContainer = new Container bpmLabelContainer = new Container
{ {

View File

@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Select.Options namespace osu.Game.Screens.Select.Options
{ {
@ -76,6 +77,7 @@ namespace osu.Game.Screens.Select.Options
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
public BeatmapOptionsButton() public BeatmapOptionsButton()
: base(HoverSampleSet.Submit)
{ {
Width = width; Width = width;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;

View File

@ -16,7 +16,7 @@ namespace osu.Game.Screens
public override bool CursorVisible => false; public override bool CursorVisible => false;
public override bool AllowRateAdjustments => false; public override bool AllowTrackAdjustments => false;
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled;
} }

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Users.Drawables namespace osu.Game.Users.Drawables
{ {
@ -71,6 +72,11 @@ namespace osu.Game.Users.Drawables
{ {
private LocalisableString tooltip = default_tooltip_text; private LocalisableString tooltip = default_tooltip_text;
public ClickableArea()
: base(HoverSampleSet.Submit)
{
}
public override LocalisableString TooltipText public override LocalisableString TooltipText
{ {
get => Enabled.Value ? tooltip : default; get => Enabled.Value ? tooltip : default;

View File

@ -5,6 +5,7 @@ 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.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
namespace osu.Game.Users.Drawables namespace osu.Game.Users.Drawables
@ -32,9 +33,17 @@ namespace osu.Game.Users.Drawables
if (country == null && !ShowPlaceholderOnNull) if (country == null && !ShowPlaceholderOnNull)
return null; return null;
return new DrawableFlag(country) return new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new DrawableFlag(country)
{
RelativeSizeAxes = Axes.Both
},
new HoverClickSounds(HoverSampleSet.Submit)
}
}; };
} }

View File

@ -31,6 +31,7 @@ namespace osu.Game.Users
protected Drawable Background { get; private set; } protected Drawable Background { get; private set; }
protected UserPanel(User user) protected UserPanel(User user)
: base(HoverSampleSet.Submit)
{ {
if (user == null) if (user == null)
throw new ArgumentNullException(nameof(user)); throw new ArgumentNullException(nameof(user));

View File

@ -22,7 +22,7 @@
<PackageReference Include="DiffPlex" Version="1.7.0" /> <PackageReference Include="DiffPlex" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.34" /> <PackageReference Include="HtmlAgilityPack" Version="1.11.34" />
<PackageReference Include="Humanizer" Version="2.11.10" /> <PackageReference Include="Humanizer" Version="2.11.10" />
<PackageReference Include="MessagePack" Version="2.2.113" /> <PackageReference Include="MessagePack" Version="2.3.75" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.8" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.8" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.8" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.8" />
@ -37,8 +37,8 @@
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.3.0" /> <PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.728.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.728.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.722.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.730.0" />
<PackageReference Include="Sentry" Version="3.8.2" /> <PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

View File

@ -71,7 +71,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.728.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.728.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.722.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.730.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup> <PropertyGroup>

View File

@ -117,7 +117,7 @@
</ImageAsset> </ImageAsset>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Xamarin.Essentials" Version="1.6.1" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project> </Project>