1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 14:53:21 +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
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" />
</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" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">

View File

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

View File

@ -130,7 +130,8 @@ namespace osu.Game.Rulesets.Catch
return new Mod[]
{
new MultiMod(new ModWindUp(), new ModWindDown()),
new CatchModFloatingFruits()
new CatchModFloatingFruits(),
new CatchModMuted(),
};
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:
return new Mod[]
{
new MultiMod(new ModWindUp(), new ModWindDown())
new MultiMod(new ModWindUp(), new ModWindDown()),
new ManiaModMuted(),
};
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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
@ -16,7 +14,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
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.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@ -67,11 +63,6 @@ namespace osu.Game.Rulesets.Osu.Mods
/// </summary>
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>
/// The extent of rotation towards playfield centre when a circle is near the edge
/// </summary>
@ -341,46 +332,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
drawableRuleset.Overlays.Add(new TargetBeatContainer(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();
}
drawableRuleset.Overlays.Add(new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime));
}
#endregion

View File

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

View File

@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.UI
private void onJudgementLoaded(DrawableOsuJudgement judgement)
{
judgementAboveHitObjectLayer.Add(judgement.GetProxyAboveHitObjectsContent());
judgementAboveHitObjectLayer.Add(judgement.ProxiedAboveHitObjectsContent);
}
[BackgroundDependencyLoader(true)]
@ -150,6 +150,10 @@ namespace osu.Game.Rulesets.Osu.UI
DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject));
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);

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:
return new Mod[]
{
new MultiMod(new ModWindUp(), new ModWindDown())
new MultiMod(new ModWindUp(), new ModWindDown()),
new TaikoModMuted(),
};
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)
{
await AllowImport.Task;
return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken));
await AllowImport.Task.ConfigureAwait(false);
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)
{
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.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneLabelledColourPalette : OsuTestScene
public class TestSceneLabelledColourPalette : OsuManualInputManagerTestScene
{
private LabelledColourPalette component;
@ -30,21 +35,41 @@ namespace osu.Game.Tests.Visual.UserInterface
}, 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)
{
AddStep("create component", () =>
{
Child = new Container
Child = new OsuContextMenuContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 500,
AutoSizeAxes = Axes.Y,
Child = component = new LabelledColourPalette
RelativeSizeAxes = Axes.Both,
Child = new Container
{
Anchor = 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[]
{
Color4.DarkRed,
Color4.Aquamarine,
Color4.Goldenrod,
Color4.Gainsboro
Colour4.DarkRed,
Colour4.Aquamarine,
Colour4.Goldenrod,
Colour4.Gainsboro
});
});
}
private Color4 randomColour() => new Color4(
private Colour4 randomColour() => new Color4(
RNG.NextSingle(),
RNG.NextSingle(),
RNG.NextSingle(),
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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Game.Tournament.Configuration;
using osu.Game.Tests;
using osu.Game.Tournament.Configuration;
namespace osu.Game.Tournament.Tests.NonVisual
{
[TestFixture]
public class CustomTourneyDirectoryTest
public class CustomTourneyDirectoryTest : TournamentHostTest
{
[Test]
public void TestDefaultDirectory()
@ -24,7 +21,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
{
try
{
var osu = loadOsu(host);
var osu = LoadTournament(host);
var storage = osu.Dependencies.Get<Storage>();
Assert.That(storage.GetFullPath("."), Is.EqualTo(Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default")));
@ -54,7 +51,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
try
{
var osu = loadOsu(host);
var osu = LoadTournament(host);
storage = osu.Dependencies.Get<Storage>();
@ -111,7 +108,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
try
{
var osu = loadOsu(host);
var osu = LoadTournament(host);
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);
}
}

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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework;
using osu.Framework.Allocation;
@ -15,7 +12,7 @@ using osu.Game.Tournament.IPC;
namespace osu.Game.Tournament.Tests.NonVisual
{
[TestFixture]
public class IPCLocationTest
public class IPCLocationTest : TournamentHostTest
{
[Test]
public void CheckIPCLocation()
@ -34,11 +31,11 @@ namespace osu.Game.Tournament.Tests.NonVisual
try
{
var osu = loadOsu(host);
var osu = LoadTournament(host);
TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get<Storage>();
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(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.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -11,6 +10,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
@ -198,8 +198,8 @@ namespace osu.Game.Tournament.Components
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DiffPiece(("Length", TimeSpan.FromMilliseconds(length).ToString(@"mm\:ss"))),
new DiffPiece(("BPM", $"{bpm:0.#}"))
new DiffPiece(("Length", length.ToFormattedDuration().ToString())),
new DiffPiece(("BPM", $"{bpm:0.#}")),
}
},
new Container

View File

@ -27,6 +27,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
{
var teams = new List<TournamentTeam>();
if (!storage.Exists(teams_filename))
return teams;
try
{
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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Drawings.Components;
@ -51,6 +53,29 @@ namespace osu.Game.Tournament.Screens.Drawings
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;
}

View File

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

View File

@ -1,2 +1,3 @@
[*.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.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
@ -25,6 +26,9 @@ namespace osu.Game.Graphics.Containers
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
[Resolved]
private GameHost host { get; set; }
public void AddLinks(string text, List<Link> links)
{
if (string.IsNullOrEmpty(text) || links == null)
@ -91,8 +95,11 @@ namespace osu.Game.Graphics.Containers
{
if (action != null)
action();
else
game?.HandleLink(link);
else if (game != null)
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.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
@ -59,33 +60,37 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new GridContainer
InternalChildren = new Drawable[]
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[]
new GridContainer
{
new[]
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[]
{
handleContainer = new Container
new[]
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5 },
Child = handle = new PlaylistItemHandle
handleContainer = new Container
{
Size = new Vector2(12),
Colour = HandleColour,
AlwaysPresent = true,
Alpha = 0
}
},
CreateContent()
}
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5 },
Child = handle = new PlaylistItemHandle
{
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) },
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
new HoverClickSounds()
};
}

View File

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

View File

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

View File

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

View File

@ -10,8 +10,8 @@ namespace osu.Game.Graphics.UserInterface
[Description("default")]
Default,
[Description("soft")]
Soft,
[Description("submit")]
Submit,
[Description("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;
Shear = shear;

View File

@ -1,32 +1,39 @@
// 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2
{
/// <summary>
/// A component which displays a colour along with related description text.
/// </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;
public Bindable<Color4> Current
public Bindable<Colour4> Current
{
get => current.Current;
set => current.Current = value;
@ -62,24 +69,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new CircularContainer
new ColourCircle
{
RelativeSizeAxes = Axes.X,
Height = 100,
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)
}
}
Current = { BindTarget = Current },
DeleteRequested = () => DeleteRequested?.Invoke(this)
},
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()
{
fill.Colour = current.Value;
colourHexCode.Text = current.Value.ToHex();
colourHexCode.Colour = OsuColour.ForegroundTextColourFor(current.Value);
private readonly Box fill;
private readonly OsuSpriteText colourHexCode;
public ColourCircle()
{
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.
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
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 osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2
{
@ -17,7 +22,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// </summary>
public class ColourPalette : CompositeDrawable
{
public BindableList<Color4> Colours { get; } = new BindableList<Color4>();
public BindableList<Colour4> Colours { get; } = new BindableList<Colour4>();
private string colourNamePrefix = "Colour";
@ -36,36 +41,24 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
}
private FillFlowContainer<ColourDisplay> palette;
private Container placeholder;
private FillFlowContainer palette;
private IEnumerable<ColourDisplay> colourDisplays => palette.OfType<ColourDisplay>();
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
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,
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)
}
}
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10),
Direction = FillDirection.Full
};
}
@ -73,7 +66,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
{
base.LoadComplete();
Colours.BindCollectionChanged((_, __) => updatePalette(), true);
Colours.BindCollectionChanged((_, args) =>
{
if (args.Action != NotifyCollectionChangedAction.Replace)
updatePalette();
}, true);
FinishTransforms(true);
}
@ -83,37 +80,103 @@ namespace osu.Game.Graphics.UserInterfaceV2
{
palette.Clear();
if (Colours.Any())
for (int i = 0; i < Colours.Count; ++i)
{
palette.FadeIn(fade_duration, Easing.OutQuint);
placeholder.FadeOut(fade_duration, Easing.OutQuint);
}
else
{
palette.FadeOut(fade_duration, Easing.OutQuint);
placeholder.FadeIn(fade_duration, Easing.OutQuint);
// copy to avoid accesses to modified closure.
int colourIndex = i;
ColourDisplay display;
palette.Add(display = new ColourDisplay
{
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
{
Current = { Value = item }
});
}
Action = () => Colours.Add(Colour4.White)
});
reindexItems();
}
private void colourDeletionRequested(ColourDisplay display) => Colours.RemoveAt(palette.IndexOf(display));
private void reindexItems()
{
int index = 1;
foreach (var colour in palette)
foreach (var colourDisplay in colourDisplays)
{
colour.ColourName = $"{colourNamePrefix} {index}";
colourDisplay.ColourName = $"{colourNamePrefix} {index}";
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.
using osu.Framework.Bindables;
using osuTK.Graphics;
using osu.Framework.Graphics;
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
{

View File

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

View File

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

View File

@ -150,7 +150,7 @@ namespace osu.Game.Online.API
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");
Logout();

View File

@ -86,8 +86,6 @@ namespace osu.Game.Online.API
/// </summary>
private APIRequestCompletionState completionState;
private Action pendingFailure;
public void Perform(IAPIProvider api)
{
if (!(api is APIAccess apiAccess))
@ -99,29 +97,23 @@ namespace osu.Game.Online.API
API = apiAccess;
User = apiAccess.LocalUser.Value;
if (checkAndScheduleFailure())
return;
if (isFailing) return;
WebRequest = CreateWebRequest();
WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
if (checkAndScheduleFailure())
return;
if (isFailing) 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())
return;
if (isFailing) return;
PostProcess();
API.Schedule(TriggerSuccess);
TriggerSuccess();
}
/// <summary>
@ -141,7 +133,10 @@ namespace osu.Game.Online.API
completionState = APIRequestCompletionState.Completed;
}
Success?.Invoke();
if (API == null)
Success?.Invoke();
else
API.Schedule(() => Success?.Invoke());
}
internal void TriggerFailure(Exception e)
@ -154,7 +149,10 @@ namespace osu.Game.Online.API
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"));
@ -163,59 +161,47 @@ namespace osu.Game.Online.API
{
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)
return;
}
WebRequest?.Abort();
WebRequest?.Abort();
// in the case of a cancellation we don't care about whether there's an error in the response.
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"""))
// in the case of a cancellation we don't care about whether there's an error in the response.
if (!(e is OperationCanceledException))
{
try
{
// attempt to decode a displayable error string.
var error = JsonConvert.DeserializeObject<DisplayableError>(responseString);
if (error != null)
e = new APIException(error.ErrorMessage, e);
}
catch
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
{
// 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);
pendingFailure = () => TriggerFailure(e);
checkAndScheduleFailure();
Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network);
TriggerFailure(e);
}
}
/// <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>
/// <returns>Whether we are in a failed or cancelled state.</returns>
private bool checkAndScheduleFailure()
private bool isFailing
{
lock (completionStateLock)
get
{
if (pendingFailure == null)
lock (completionStateLock)
return completionState == APIRequestCompletionState.Failed;
}
if (API == null)
pendingFailure();
else
API.Schedule(pendingFailure);
pendingFailure = null;
return true;
}
private class DisplayableError

View File

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

View File

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

View File

@ -479,7 +479,7 @@ namespace osu.Game
if (r.NewValue?.Available != true)
{
// 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;
}

View File

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

View File

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

View File

@ -3,6 +3,9 @@
using System;
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.Graphics;
using osu.Framework.Graphics.Containers;
@ -13,6 +16,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osuTK;
using osuTK.Graphics;
@ -39,6 +43,8 @@ namespace osu.Game.Overlays.Chat.Tabs
protected override Container<Drawable> Content => content;
private Sample sampleTabSwitched;
public ChannelTabItem(Channel 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]
private void load(OsuColour colours)
private void load(OsuColour colours, AudioManager audio)
{
BackgroundActive = colours.ChatBlue;
BackgroundInactive = colours.Gray4;
backgroundHover = colours.Gray7;
sampleTabSwitched = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
highlightBox.Colour = colours.Yellow;
}
@ -217,7 +225,14 @@ namespace osu.Game.Overlays.Chat.Tabs
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();
}
}

View File

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

View File

@ -400,35 +400,38 @@ namespace osu.Game.Overlays
NextTrack();
}
private bool allowRateAdjustments;
private bool allowTrackAdjustments;
/// <summary>
/// Whether mod rate adjustments are allowed to be applied.
/// Whether mod track adjustments are allowed to be applied.
/// </summary>
public bool AllowRateAdjustments
public bool AllowTrackAdjustments
{
get => allowRateAdjustments;
get => allowTrackAdjustments;
set
{
if (allowRateAdjustments == value)
if (allowTrackAdjustments == value)
return;
allowRateAdjustments = value;
allowTrackAdjustments = value;
ResetTrackAdjustments();
}
}
/// <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>
/// <remarks>
/// Does not reset speed adjustments applied directly to the beatmap track.
/// Does not reset any adjustments applied directly to the beatmap track.
/// </remarks>
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>())
mod.ApplyToTrack(CurrentTrack);

View File

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

View File

@ -15,13 +15,18 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using System.Diagnostics;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Platform;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.News.Sidebar
{
public class MonthSection : CompositeDrawable
{
private const int animation_duration = 250;
private Sample sampleOpen;
private Sample sampleClose;
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
@ -59,6 +79,8 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly SpriteIcon icon;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
public DropdownHeader(int month, int year)
{
var date = new DateTime(year, month, 1);
@ -104,6 +126,7 @@ namespace osu.Game.Overlays.News.Sidebar
private readonly APINewsPost post;
public PostButton(APINewsPost post)
: base(HoverSampleSet.Submit)
{
this.post = post;

View File

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

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
@ -52,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
new OsuSpriteText
{
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.Game.Rulesets;
using osu.Game.Users;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;
using System;
namespace osu.Game.Overlays.Rankings
{
@ -29,13 +32,14 @@ namespace osu.Game.Overlays.Rankings
{
public RankingsTitle()
{
Title = "ranking";
Title = PageTitleStrings.MainRankingControllerDefault;
Description = "find out who's the best right now";
IconTexture = "Icons/Hexacons/rankings";
}
}
}
[LocalisableEnum(typeof(RankingsScopeEnumLocalisationMapper))]
public enum RankingsScope
{
Performance,
@ -43,4 +47,28 @@ namespace osu.Game.Overlays.Rankings
Score,
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.
// 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
{
public class RankingsSortTabControl : OverlaySortTabControl<RankingsSortCriteria>
{
public RankingsSortTabControl()
{
Title = "Show";
Title = RankingsStrings.FilterTitle.ToUpper();
}
}
[LocalisableEnum(typeof(RankingsSortCriteriaEnumLocalisationMapper))]
public enum RankingsSortCriteria
{
All,
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.Game.Online.API.Requests;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Rankings
{
@ -92,10 +94,10 @@ namespace osu.Game.Overlays.Rankings
Margin = new MarginPadding { Bottom = 5 },
Children = new Drawable[]
{
startDateColumn = new InfoColumn(@"Start Date"),
endDateColumn = new InfoColumn(@"End Date"),
mapCountColumn = new InfoColumn(@"Map Count"),
participantsColumn = new InfoColumn(@"Participants")
startDateColumn = new InfoColumn(RankingsStrings.SpotlightStartDate),
endDateColumn = new InfoColumn(RankingsStrings.SpotlightEndDate),
mapCountColumn = new InfoColumn(RankingsStrings.SpotlightMapCount),
participantsColumn = new InfoColumn(RankingsStrings.SpotlightParticipants)
}
},
new RankingsSortTabControl
@ -122,22 +124,22 @@ namespace osu.Game.Overlays.Rankings
{
startDateColumn.Value = dateToString(response.Spotlight.StartDate);
endDateColumn.Value = dateToString(response.Spotlight.EndDate);
mapCountColumn.Value = response.BeatmapSets.Count.ToString();
participantsColumn.Value = response.Spotlight.Participants?.ToString("N0");
mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"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
{
public string Value
public LocalisableString Value
{
set => valueText.Text = value;
}
private readonly OsuSpriteText valueText;
public InfoColumn(string name)
public InfoColumn(LocalisableString name)
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Vertical;

View File

@ -9,6 +9,8 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Resources.Localisation.Web;
using osu.Framework.Localisation;
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 TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Avg. Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Performance", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("Avg. Perf.", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatActiveUsers, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatRankedScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatAverageScore, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatPerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize), true),
new RankingsTableColumn(RankingsStrings.StatAveragePerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
};
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[]
{
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
{
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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
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[]
{
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 }));
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();
}
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 TableColumn(string.Empty, Anchor.CentreLeft, new Dimension()), // flag and username (country name)
new RankingsTableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 40)), // place
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 virtual string HighlightedColumn => @"Performance";
protected override Drawable CreateHeader(int index, TableColumn column)
{
var title = column?.Header ?? default;
return new HeaderText(title, title == HighlightedColumn);
}
protected sealed override Drawable CreateHeader(int index, TableColumn column)
=> (column as RankingsTableColumn)?.CreateHeaderText() ?? new HeaderText(column?.Header ?? default, false);
protected abstract Country GetCountry(TModel item);
@ -85,7 +80,7 @@ namespace osu.Game.Overlays.Rankings.Tables
private OsuSpriteText createIndexDrawable(int index) => new RowText
{
Text = $"#{index + 1}",
Text = (index + 1).ToLocalisableString(@"\##"),
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
{
private readonly bool isHighlighted;
@ -136,7 +144,7 @@ namespace osu.Game.Overlays.Rankings.Tables
}
}
protected class ColoredRowText : RowText
protected class ColouredRowText : RowText
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)

View File

@ -4,6 +4,8 @@
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
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 TableColumn("Ranked Score", Anchor.Centre, new Dimension(GridSizeMode.AutoSize))
new RankingsTableColumn(RankingsStrings.StatTotalScore, 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[]
{
new ColoredRowText
new ColouredRowText
{
Text = $@"{item.TotalScore:N0}",
Text = item.TotalScore.ToLocalisableString(@"N0"),
},
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.Scoring;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
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 TableColumn("Play Count", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatAccuracy, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
new RankingsTableColumn(RankingsStrings.StatPlayCount, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
}.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();
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 Drawable CreateFlagContent(UserStatistics item)
@ -52,28 +47,38 @@ namespace osu.Game.Overlays.Rankings.Tables
protected sealed override Drawable[] CreateAdditionalContent(UserStatistics item) => new[]
{
new ColoredRowText { Text = item.DisplayAccuracy, },
new ColoredRowText { Text = $@"{item.PlayCount:N0}", },
new ColouredRowText { Text = item.DisplayAccuracy, },
new ColouredRowText { Text = item.PlayCount.ToLocalisableString(@"N0") },
}.Concat(CreateUniqueContent(item)).Concat(new[]
{
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]:N0}", },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]:N0}", },
new ColoredRowText { Text = $@"{item.GradesCount[ScoreRank.A]:N0}", }
new ColouredRowText { Text = (item.GradesCount[ScoreRank.XH] + item.GradesCount[ScoreRank.X]).ToLocalisableString(@"N0"), },
new ColouredRowText { Text = (item.GradesCount[ScoreRank.SH] + item.GradesCount[ScoreRank.S]).ToLocalisableString(@"N0"), },
new ColouredRowText { Text = item.GradesCount[ScoreRank.A].ToLocalisableString(@"N0"), }
}).ToArray();
protected abstract TableColumn[] CreateUniqueHeaders();
protected abstract RankingsTableColumn[] CreateUniqueHeaders();
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)
{
Margin = new MarginPadding
{
// Grade columns have extra horizontal padding for readibility
Horizontal = isGrade ? 20 : 10,
Horizontal = 20,
Vertical = 5
};
}

View File

@ -138,7 +138,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
},
}
}
}
},
new HoverClickSounds()
};
foreach (var b in bindings)
@ -458,6 +459,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Origin = Anchor.Centre,
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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@ -31,6 +32,9 @@ namespace osu.Game.Rulesets.Judgements
private readonly Container aboveHitObjectsContent;
private readonly Lazy<Drawable> proxiedAboveHitObjectsContent;
public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value;
/// <summary>
/// Creates a drawable which visualises a <see cref="Judgements.Judgement"/>.
/// </summary>
@ -52,6 +56,8 @@ namespace osu.Game.Rulesets.Judgements
Depth = float.MinValue,
RelativeSizeAxes = Axes.Both
});
proxiedAboveHitObjectsContent = new Lazy<Drawable>(() => aboveHitObjectsContent.CreateProxy());
}
[BackgroundDependencyLoader]
@ -60,8 +66,6 @@ namespace osu.Game.Rulesets.Judgements
prepareDrawables();
}
public Drawable GetProxyAboveHitObjectsContent() => aboveHitObjectsContent.CreateProxy();
/// <summary>
/// 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.

View File

@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToHealthProcessor : IApplicableMod
{
/// <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>
void ApplyToHealthProcessor(HealthProcessor healthProcessor);
}

View File

@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
public interface IApplicableToScoreProcessor : IApplicableMod
{
/// <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>
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.
// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Handlers;
using osu.Game.Overlays;
using osu.Game.Replays;
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.Scoring;
using osu.Game.Screens.Play;
@ -98,6 +99,14 @@ namespace osu.Game.Rulesets.UI
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>
/// Creates a ruleset visualisation for the provided ruleset and beatmap.
/// </summary>
@ -155,23 +164,28 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader]
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,
Children = new Drawable[]
FrameStableComponents,
audioContainer = new AudioContainer
{
FrameStableComponents,
KeyBindingInputManager
RelativeSizeAxes = Axes.Both,
Child = KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield)
),
Overlays,
}
},
},
Overlays,
}
};
Audio = audioContainer;
if ((ResumeOverlay = CreateResumeOverlay()) != null)
{
AddInternal(CreateInputManager()

View File

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

View File

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

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
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;
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; }
/// <summary>
/// Whether mod rate adjustments are allowed to be applied.
/// Whether mod track adjustments are allowed to be applied.
/// </summary>
bool AllowRateAdjustments { get; }
bool AllowTrackAdjustments { get; }
/// <summary>
/// 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 AllowRateAdjustments => false;
public override bool AllowTrackAdjustments => false;
private Screen songSelect;

View File

@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public override bool DisallowExternalBeatmapRulesetChanges => true;
// 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>
/// Whether all spectating players have finished loading.

View File

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

View File

@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
// 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);
@ -297,11 +297,19 @@ namespace osu.Game.Screens.Play
ScoreProcessor.HasCompleted.BindValueChanged(scoreCompletionChanged);
HealthProcessor.Failed += onFail;
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
// Provide judgement processors to mods after they're loaded so that they're on the gameplay clock,
// 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>())
mod.ApplyToHealthProcessor(HealthProcessor);
HealthProcessor.OnLoadComplete += _ =>
{
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>())
mod.ApplyToHealthProcessor(HealthProcessor);
};
IsBreakTime.BindTo(breakTracker.IsBreakTime);
IsBreakTime.BindValueChanged(onBreakTimeChanged, true);

View File

@ -178,7 +178,7 @@ namespace osu.Game.Screens.Play
float barHeight = bottom_bar_height + handle_size.Y;
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();
}

View File

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

View File

@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
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 BeatmapOptionsButton()
: base(HoverSampleSet.Submit)
{
Width = width;
RelativeSizeAxes = Axes.Y;

View File

@ -16,7 +16,7 @@ namespace osu.Game.Screens
public override bool CursorVisible => false;
public override bool AllowRateAdjustments => false;
public override bool AllowTrackAdjustments => false;
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.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Users.Drawables
{
@ -71,6 +72,11 @@ namespace osu.Game.Users.Drawables
{
private LocalisableString tooltip = default_tooltip_text;
public ClickableArea()
: base(HoverSampleSet.Submit)
{
}
public override LocalisableString TooltipText
{
get => Enabled.Value ? tooltip : default;

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Users.Drawables
@ -32,9 +33,17 @@ namespace osu.Game.Users.Drawables
if (country == null && !ShowPlaceholderOnNull)
return null;
return new DrawableFlag(country)
return new Container
{
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 UserPanel(User user)
: base(HoverSampleSet.Submit)
{
if (user == null)
throw new ArgumentNullException(nameof(user));

View File

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

View File

@ -71,7 +71,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<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>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup>

View File

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