Skip to content

Commit

Permalink
Merge branch 'master' into beatmap-carousel-v2
Browse files Browse the repository at this point in the history
  • Loading branch information
peppy committed Jan 14, 2025
2 parents cf55fe1 + b4d054f commit 8d41eda
Show file tree
Hide file tree
Showing 25 changed files with 557 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ You can also generally download a version for your current device from the [osu!

If your platform is unsupported or not listed above, there is still a chance you can run the release or manually build it by following the instructions below.

**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores in early 2024.
**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores very soon so we don't have to live with this limitation.

## Developing a custom ruleset

Expand Down
2 changes: 1 addition & 1 deletion osu.Android.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1224.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.114.1" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
Expand Down
11 changes: 8 additions & 3 deletions osu.Desktop/OsuGameDesktop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ public OsuGameDesktop(string[]? args = null)
{
try
{
stableInstallPath = getStableInstallPathFromRegistry();
stableInstallPath = getStableInstallPathFromRegistry("osustable.File.osz");

if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
return stableInstallPath;

stableInstallPath = getStableInstallPathFromRegistry("osu!");

if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
return stableInstallPath;
Expand All @@ -89,9 +94,9 @@ public OsuGameDesktop(string[]? args = null)
}

[SupportedOSPlatform("windows")]
private string? getStableInstallPathFromRegistry()
private string? getStableInstallPathFromRegistry(string progId)
{
using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu!"))
using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey(progId))
return key?.OpenSubKey(WindowsAssociationManager.SHELL_OPEN_COMMAND)?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
}

Expand Down
129 changes: 129 additions & 0 deletions osu.Game.Tests/Visual/Components/TestSceneFriendPresenceNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat;
using osu.Game.Online.Metadata;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Users;
using osuTK.Input;

namespace osu.Game.Tests.Visual.Components
{
public partial class TestSceneFriendPresenceNotifier : OsuManualInputManagerTestScene
{
private ChannelManager channelManager = null!;
private NotificationOverlay notificationOverlay = null!;
private ChatOverlay chatOverlay = null!;
private TestMetadataClient metadataClient = null!;

[SetUp]
public void Setup() => Schedule(() =>
{
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies =
[
(typeof(ChannelManager), channelManager = new ChannelManager(API)),
(typeof(INotificationOverlay), notificationOverlay = new NotificationOverlay()),
(typeof(ChatOverlay), chatOverlay = new ChatOverlay()),
(typeof(MetadataClient), metadataClient = new TestMetadataClient()),
],
Children = new Drawable[]
{
channelManager,
notificationOverlay,
chatOverlay,
metadataClient,
new FriendPresenceNotifier()
}
};

for (int i = 1; i <= 100; i++)
((DummyAPIAccess)API).Friends.Add(new APIRelation { TargetID = i, TargetUser = new APIUser { Username = $"Friend {i}" } });
});

[Test]
public void TestNotifications()
{
AddStep("bring friend 1 online", () => metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online }));
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
AddStep("bring friend 1 offline", () => metadataClient.FriendPresenceUpdated(1, null));
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(2));
}

[Test]
public void TestSingleUserNotificationOpensChat()
{
AddStep("bring friend 1 online", () => metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online }));
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));

AddStep("click notification", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<Notification>().First());
InputManager.Click(MouseButton.Left);
});

AddUntilStep("chat overlay opened", () => chatOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddUntilStep("user channel selected", () => channelManager.CurrentChannel.Value.Name, () => Is.EqualTo(((DummyAPIAccess)API).Friends[0].TargetUser!.Username));
}

[Test]
public void TestMultipleUserNotificationDoesNotOpenChat()
{
AddStep("bring friends 1 & 2 online", () =>
{
metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online });
metadataClient.FriendPresenceUpdated(2, new UserPresence { Status = UserStatus.Online });
});

AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));

AddStep("click notification", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<Notification>().First());
InputManager.Click(MouseButton.Left);
});

AddAssert("chat overlay not opened", () => chatOverlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
}

[Test]
public void TestNonFriendsDoNotNotify()
{
AddStep("bring non-friend 1000 online", () => metadataClient.UserPresenceUpdated(1000, new UserPresence { Status = UserStatus.Online }));
AddWaitStep("wait for possible notification", 10);
AddAssert("no notification", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
}

[Test]
public void TestPostManyDebounced()
{
AddStep("bring friends 1-10 online", () =>
{
for (int i = 1; i <= 10; i++)
metadataClient.FriendPresenceUpdated(i, new UserPresence { Status = UserStatus.Online });
});

AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));

AddStep("bring friends 1-10 offline", () =>
{
for (int i = 1; i <= 10; i++)
metadataClient.FriendPresenceUpdated(i, null);
});

AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(2));
}
}
}
72 changes: 72 additions & 0 deletions osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using System.Linq;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
Expand Down Expand Up @@ -102,6 +103,77 @@ public void TestMutateProtectedSkinDuringGameplay()
AddUntilStep("current skin is mutable", () => !Game.Dependencies.Get<SkinManager>().CurrentSkin.Value.SkinInfo.Value.Protected);
}

[Test]
public void TestMutateProtectedSkinFromMainMenu_UndoToInitialStateIsCorrect()
{
AddStep("set default skin", () => Game.Dependencies.Get<SkinManager>().CurrentSkinInfo.SetDefault());
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());

openSkinEditor();
AddUntilStep("current skin is mutable", () => !Game.Dependencies.Get<SkinManager>().CurrentSkin.Value.SkinInfo.Value.Protected);

AddUntilStep("wait for player", () =>
{
DismissAnyNotifications();
return Game.ScreenStack.CurrentScreen is Player;
});

string state = string.Empty;

AddUntilStep("wait for accuracy counter", () => Game.ChildrenOfType<ArgonAccuracyCounter>().Any(counter => counter.Position != new Vector2()));
AddStep("dump state of accuracy meter", () => state = JsonConvert.SerializeObject(Game.ChildrenOfType<ArgonAccuracyCounter>().First().CreateSerialisedInfo()));
AddStep("add any component", () => Game.ChildrenOfType<SkinComponentToolbox.ToolboxComponentButton>().First().TriggerClick());
AddStep("undo", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Z);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddUntilStep("only one accuracy meter left",
() => Game.ChildrenOfType<Player>().Single().ChildrenOfType<ArgonAccuracyCounter>().Count(),
() => Is.EqualTo(1));
AddAssert("accuracy meter state unchanged",
() => JsonConvert.SerializeObject(Game.ChildrenOfType<ArgonAccuracyCounter>().First().CreateSerialisedInfo()),
() => Is.EqualTo(state));
}

[Test]
public void TestMutateProtectedSkinFromPlayer_UndoToInitialStateIsCorrect()
{
AddStep("set default skin", () => Game.Dependencies.Get<SkinManager>().CurrentSkinInfo.SetDefault());
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
advanceToSongSelect();
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);

AddStep("enable NF", () => Game.SelectedMods.Value = new[] { new OsuModNoFail() });
AddStep("enter gameplay", () => InputManager.Key(Key.Enter));

AddUntilStep("wait for player", () =>
{
DismissAnyNotifications();
return Game.ScreenStack.CurrentScreen is Player;
});
openSkinEditor();

string state = string.Empty;

AddUntilStep("wait for accuracy counter", () => Game.ChildrenOfType<ArgonAccuracyCounter>().Any(counter => counter.Position != new Vector2()));
AddStep("dump state of accuracy meter", () => state = JsonConvert.SerializeObject(Game.ChildrenOfType<ArgonAccuracyCounter>().First().CreateSerialisedInfo()));
AddStep("add any component", () => Game.ChildrenOfType<SkinComponentToolbox.ToolboxComponentButton>().First().TriggerClick());
AddStep("undo", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Z);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddUntilStep("only one accuracy meter left",
() => Game.ChildrenOfType<Player>().Single().ChildrenOfType<ArgonAccuracyCounter>().Count(),
() => Is.EqualTo(1));
AddAssert("accuracy meter state unchanged",
() => JsonConvert.SerializeObject(Game.ChildrenOfType<ArgonAccuracyCounter>().First().CreateSerialisedInfo()),
() => Is.EqualTo(state));
}

[Test]
public void TestComponentsDeselectedOnSkinEditorHide()
{
Expand Down
2 changes: 2 additions & 0 deletions osu.Game/Configuration/OsuConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ protected override void InitialiseDefaults()

SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
SetDefault(OsuSetting.NotifyOnPrivateMessage, true);
SetDefault(OsuSetting.NotifyOnFriendPresenceChange, true);

// Audio
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
Expand Down Expand Up @@ -418,6 +419,7 @@ public enum OsuSetting
IntroSequence,
NotifyOnUsernameMentioned,
NotifyOnPrivateMessage,
NotifyOnFriendPresenceChange,
UIHoldActivationDelay,
HitLighting,
StarFountains,
Expand Down
8 changes: 4 additions & 4 deletions osu.Game/Graphics/UserInterface/OsuNumberBox.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Input;

namespace osu.Game.Graphics.UserInterface
{
public partial class OsuNumberBox : OsuTextBox
{
protected override bool AllowIme => false;

public OsuNumberBox()
{
InputProperties = new TextInputProperties(TextInputType.Number, false);

SelectAllOnFocus = true;
}

protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
}
}
10 changes: 3 additions & 7 deletions osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

namespace osu.Game.Graphics.UserInterface
{
public partial class OsuPasswordTextBox : OsuTextBox, ISuppressKeyEventLogging
public partial class OsuPasswordTextBox : OsuTextBox
{
protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer
{
Expand All @@ -28,19 +28,15 @@ public partial class OsuPasswordTextBox : OsuTextBox, ISuppressKeyEventLogging

protected override bool AllowUniqueCharacterSamples => false;

protected override bool AllowClipboardExport => false;

protected override bool AllowWordNavigation => false;

protected override bool AllowIme => false;

private readonly CapsWarning warning;

[Resolved]
private GameHost host { get; set; } = null!;

public OsuPasswordTextBox()
{
InputProperties = new TextInputProperties(TextInputType.Password, false);

Add(warning = new CapsWarning
{
Size = new Vector2(20),
Expand Down
6 changes: 6 additions & 0 deletions osu.Game/Graphics/UserInterfaceV2/FormNumberBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System.Globalization;
using osu.Framework.Input;

namespace osu.Game.Graphics.UserInterfaceV2
{
Expand All @@ -19,6 +20,11 @@ internal partial class InnerNumberBox : InnerTextBox
{
public bool AllowDecimals { get; init; }

public InnerNumberBox()
{
InputProperties = new TextInputProperties(TextInputType.Number, false);
}

protected override bool CanAddCharacter(char character)
=> char.IsAsciiDigit(character) || (AllowDecimals && CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator.Contains(character));
}
Expand Down
12 changes: 11 additions & 1 deletion osu.Game/Localisation/OnlineSettingsStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ public static class OnlineSettingsStrings
/// </summary>
public static LocalisableString NotifyOnPrivateMessage => new TranslatableString(getKey(@"notify_on_private_message"), @"Show a notification when you receive a private message");

/// <summary>
/// "Show notification popups when friends change status"
/// </summary>
public static LocalisableString NotifyOnFriendPresenceChange => new TranslatableString(getKey(@"notify_on_friend_presence_change"), @"Show notification popups when friends change status");

/// <summary>
/// "Notifications will be shown when friends go online/offline."
/// </summary>
public static LocalisableString NotifyOnFriendPresenceChangeTooltip => new TranslatableString(getKey(@"notify_on_friend_presence_change_tooltip"), @"Notifications will be shown when friends go online/offline.");

/// <summary>
/// "Integrations"
/// </summary>
Expand Down Expand Up @@ -84,6 +94,6 @@ public static class OnlineSettingsStrings
/// </summary>
public static LocalisableString HideCountryFlags => new TranslatableString(getKey(@"hide_country_flags"), @"Hide country flags");

private static string getKey(string key) => $"{prefix}:{key}";
private static string getKey(string key) => $@"{prefix}:{key}";
}
}
11 changes: 9 additions & 2 deletions osu.Game/Online/API/APIAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
Expand Down Expand Up @@ -610,8 +611,14 @@ public void UpdateLocalFriends()
friendsReq.Failure += _ => state.Value = APIState.Failing;
friendsReq.Success += res =>
{
friends.Clear();
friends.AddRange(res);
var existingFriends = friends.Select(f => f.TargetID).ToHashSet();
var updatedFriends = res.Select(f => f.TargetID).ToHashSet();

// Add new friends into local list.
friends.AddRange(res.Where(r => !existingFriends.Contains(r.TargetID)));

// Remove non-friends from local list.
friends.RemoveAll(f => !updatedFriends.Contains(f.TargetID));
};

Queue(friendsReq);
Expand Down
Loading

0 comments on commit 8d41eda

Please sign in to comment.