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

Merge branch 'master' into localisation-settings

This commit is contained in:
Dean Herbert 2022-09-19 23:37:26 +09:00 committed by GitHub
commit bd28c6f96d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 141 additions and 110 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.908.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.916.1" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

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 NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
@ -125,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
/// Ensures alternation is reset before the first hitobject after a break.
/// </summary>
[Test]
public void TestInputSingularWithBreak() => CreateModTest(new ModTestData
public void TestInputSingularWithBreak([Values] bool pressBeforeSecondObject) => CreateModTest(new ModTestData
{
Mod = new OsuModAlternate(),
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2,
@ -155,21 +156,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
},
}
},
ReplayFrames = new List<ReplayFrame>
ReplayFrames = new ReplayFrame[]
{
// first press to start alternate lock.
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(501, new Vector2(100)),
// press same key after break but before hit object.
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton),
new OsuReplayFrame(2251, new Vector2(300, 100)),
new OsuReplayFrame(450, new Vector2(100), OsuAction.LeftButton),
new OsuReplayFrame(451, new Vector2(100)),
// press same key at second hitobject and ensure it has been hit.
new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton),
new OsuReplayFrame(2501, new Vector2(500, 100)),
new OsuReplayFrame(2450, new Vector2(500, 100), OsuAction.LeftButton),
new OsuReplayFrame(2451, new Vector2(500, 100)),
// press same key at third hitobject and ensure it has been missed.
new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.LeftButton),
new OsuReplayFrame(3001, new Vector2(500, 100)),
}
new OsuReplayFrame(2950, new Vector2(500, 100), OsuAction.LeftButton),
new OsuReplayFrame(2951, new Vector2(500, 100)),
}.Concat(!pressBeforeSecondObject
? Enumerable.Empty<ReplayFrame>()
: new ReplayFrame[]
{
// press same key after break but before hit object.
new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton),
new OsuReplayFrame(2251, new Vector2(300, 100)),
}
).ToList()
});
}
}

View File

@ -18,7 +18,7 @@ using osu.Game.Utils;
namespace osu.Game.Rulesets.Osu.Mods
{
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>
public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IUpdatableByPlayfield
{
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) };
@ -62,15 +62,18 @@ namespace osu.Game.Rulesets.Osu.Mods
gameplayClock = drawableRuleset.FrameStableClock;
}
public void Update(Playfield playfield)
{
if (LastAcceptedAction != null && nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
LastAcceptedAction = null;
}
protected abstract bool CheckValidNewAction(OsuAction action);
private bool checkCorrectAction(OsuAction action)
{
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
{
LastAcceptedAction = null;
return true;
}
switch (action)
{

View File

@ -3,6 +3,7 @@
#nullable disable
using System;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@ -66,6 +67,18 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded);
}
[Test]
public void TestInitialZoomOutOfRange()
{
AddStep("Invalid ZoomableScrollContainer throws ArgumentException", () =>
{
Assert.Throws<ArgumentException>(() =>
{
_ = new ZoomableScrollContainer(1, 60, 0);
});
});
}
[Test]
public void TestWidthInitialization()
{

View File

@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface
displayedCount = new OsuSpriteText()
};
notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; };
notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"unread count: {count.NewValue}"; };
});
[Test]
@ -270,6 +270,8 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("wait completion", () => notification.State == ProgressNotificationState.Completed);
AddAssert("Completion toast shown", () => notificationOverlay.ToastCount == 1);
AddUntilStep("wait forwarded", () => notificationOverlay.ToastCount == 0);
AddAssert("only one unread", () => notificationOverlay.UnreadCount.Value == 1);
}
[Test]

View File

@ -51,7 +51,15 @@ namespace osu.Game.Database
ID = id;
}
public bool Equals(Live<T>? other) => ID == other?.ID;
public bool Equals(Live<T>? other)
{
if (ReferenceEquals(this, other)) return true;
if (other == null) return false;
return ID == other.ID;
}
public override int GetHashCode() => HashCode.Combine(ID);
public override string ToString() => PerformRead(i => i.ToString());
}

View File

@ -10,6 +10,9 @@ using osu.Game.Scoring;
namespace osu.Game.Online.API.Requests.Responses
{
/// <summary>
/// Represents an aggregate score for a user based off all beatmaps that have been played in the playlist.
/// </summary>
public class APIUserScoreAggregate
{
[JsonProperty("attempts")]

View File

@ -15,6 +15,8 @@ using osu.Framework.Localisation;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osu.Framework.Bindables;
using osu.Game.Configuration;
namespace osu.Game.Online.Leaderboards
{
@ -24,6 +26,7 @@ namespace osu.Game.Online.Leaderboards
private FillFlowContainer<HitResultCell> topScoreStatistics = null!;
private FillFlowContainer<HitResultCell> bottomScoreStatistics = null!;
private FillFlowContainer<ModCell> modStatistics = null!;
private readonly Bindable<bool> prefer24HourTime = new Bindable<bool>();
public LeaderboardScoreTooltip()
{
@ -36,8 +39,9 @@ namespace osu.Game.Online.Leaderboards
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OsuColour colours, OsuConfigManager configManager)
{
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
InternalChildren = new Drawable[]
{
new Box
@ -92,6 +96,13 @@ namespace osu.Game.Online.Leaderboards
};
}
protected override void LoadComplete()
{
base.LoadComplete();
prefer24HourTime.BindValueChanged(_ => updateTimestampLabel(), true);
}
private ScoreInfo? displayedScore;
public void SetContent(ScoreInfo score)
@ -101,7 +112,7 @@ namespace osu.Game.Online.Leaderboards
displayedScore = score;
timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}";
updateTimestampLabel();
modStatistics.Clear();
topScoreStatistics.Clear();
@ -121,6 +132,16 @@ namespace osu.Game.Online.Leaderboards
}
}
private void updateTimestampLabel()
{
if (displayedScore != null)
{
timestampLabel.Text = prefer24HourTime.Value
? $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy HH:mm}"
: $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy h:mm tt}";
}
}
protected override void PopIn() => this.FadeIn(20, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(80, Easing.OutQuint);

View File

@ -56,14 +56,12 @@ using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osu.Game.Updater;
using osu.Game.Users;
using osu.Game.Utils;
using osuTK.Graphics;
using Sentry;
using Logger = osu.Framework.Logging.Logger;
namespace osu.Game
{
@ -294,25 +292,13 @@ namespace osu.Game
Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ShortName;
// bind config int to database SkinInfo
configSkin = LocalConfig.GetBindable<string>(OsuSetting.Skin);
// Transfer skin from config to realm instance once on startup.
SkinManager.SetSkinFromConfiguration(configSkin.Value);
// Transfer any runtime changes back to configuration file.
SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID.ToString();
configSkin.ValueChanged += skinId =>
{
Live<SkinInfo> skinInfo = null;
if (Guid.TryParse(skinId.NewValue, out var guid))
skinInfo = SkinManager.Query(s => s.ID == guid);
if (skinInfo == null)
{
if (guid == SkinInfo.CLASSIC_SKIN)
skinInfo = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged();
}
SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLiveUnmanaged();
};
configSkin.TriggerChange();
IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true);

View File

@ -210,14 +210,14 @@ namespace osu.Game.Overlays
mainContent.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
}
private void notificationClosed()
private void notificationClosed() => Schedule(() =>
{
updateCounts();
// this debounce is currently shared between popin/popout sounds, which means one could potentially not play when the user is expecting it.
// popout is constant across all notification types, and should therefore be handled using playback concurrency instead, but seems broken at the moment.
playDebouncedSample("UI/overlay-pop-out");
}
});
private void playDebouncedSample(string sampleName)
{

View File

@ -26,7 +26,8 @@ namespace osu.Game.Overlays.Notifications
public abstract class Notification : Container
{
/// <summary>
/// User requested close.
/// Notification was closed, either by user or otherwise.
/// Importantly, this event may be fired from a non-update thread.
/// </summary>
public event Action? Closed;
@ -55,6 +56,8 @@ namespace osu.Game.Overlays.Notifications
protected Container IconContent;
public bool WasClosed { get; private set; }
private readonly Container content;
protected override Container<Drawable> Content => content;
@ -245,21 +248,23 @@ namespace osu.Game.Overlays.Notifications
initialFlash.FadeOutFromOne(2000, Easing.OutQuart);
}
public bool WasClosed;
public virtual void Close(bool runFlingAnimation)
{
if (WasClosed) return;
WasClosed = true;
if (runFlingAnimation && dragContainer.FlingLeft())
this.FadeOut(600, Easing.In);
else
this.FadeOut(100);
Closed?.Invoke();
Expire();
Schedule(() =>
{
if (runFlingAnimation && dragContainer.FlingLeft())
this.FadeOut(600, Easing.In);
else
this.FadeOut(100);
Expire();
});
}
private class DragContainer : Container

View File

@ -142,7 +142,6 @@ namespace osu.Game.Overlays.Notifications
case ProgressNotificationState.Completed:
loadingSpinner.Hide();
attemptPostCompletion();
base.Close(false);
break;
}
}
@ -166,6 +165,8 @@ namespace osu.Game.Overlays.Notifications
CompletionTarget.Invoke(CreateCompletionNotification());
completionSent = true;
Close(false);
}
private ProgressNotificationState state;
@ -239,6 +240,7 @@ namespace osu.Game.Overlays.Notifications
{
switch (State)
{
case ProgressNotificationState.Completed:
case ProgressNotificationState.Cancelled:
base.Close(runFlingAnimation);
break;

View File

@ -14,7 +14,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
@ -36,9 +35,6 @@ namespace osu.Game.Overlays.Settings.Sections
Icon = FontAwesome.Solid.PaintBrush
};
private readonly Bindable<Live<SkinInfo>> dropdownBindable = new Bindable<Live<SkinInfo>> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() };
private readonly Bindable<string> configBindable = new Bindable<string>();
private static readonly Live<SkinInfo> random_skin_info = new SkinInfo
{
ID = SkinInfo.RANDOM_SKIN,
@ -56,13 +52,14 @@ namespace osu.Game.Overlays.Settings.Sections
private IDisposable realmSubscription;
[BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuConfigManager config, [CanBeNull] SkinEditorOverlay skinEditor)
private void load([CanBeNull] SkinEditorOverlay skinEditor)
{
Children = new Drawable[]
{
skinDropdown = new SkinSettingsDropdown
{
LabelText = SkinSettingsStrings.CurrentSkin,
Current = skins.CurrentSkinInfo,
Keywords = new[] { @"skins" }
},
new SettingsButton
@ -73,47 +70,28 @@ namespace osu.Game.Overlays.Settings.Sections
new ExportSkinButton(),
new DeleteSkinButton(),
};
config.BindWith(OsuSetting.Skin, configBindable);
}
protected override void LoadComplete()
{
base.LoadComplete();
skinDropdown.Current = dropdownBindable;
realmSubscription = realm.RegisterForNotifications(_ => realm.Realm.All<SkinInfo>()
.Where(s => !s.DeletePending)
.OrderByDescending(s => s.Protected) // protected skins should be at the top.
.ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase), skinsChanged);
configBindable.BindValueChanged(_ => Scheduler.AddOnce(updateSelectedSkinFromConfig));
dropdownBindable.BindValueChanged(dropdownSelectionChanged);
}
private void dropdownSelectionChanged(ValueChangedEvent<Live<SkinInfo>> skin)
{
// Only handle cases where it's clear the user has intent to change skins.
if (skin.OldValue == null) return;
if (skin.NewValue.Equals(random_skin_info))
skinDropdown.Current.BindValueChanged(skin =>
{
var skinBefore = skins.CurrentSkinInfo.Value;
skins.SelectRandomSkin();
if (skinBefore == skins.CurrentSkinInfo.Value)
if (skin.NewValue == random_skin_info)
{
// the random selection didn't change the skin, so we should manually update the dropdown to match.
dropdownBindable.Value = skins.CurrentSkinInfo.Value;
// before selecting random, set the skin back to the previous selection.
// this is done because at this point it will be random_skin_info, and would
// cause SelectRandomSkin to be unable to skip the previous selection.
skins.CurrentSkinInfo.Value = skin.OldValue;
skins.SelectRandomSkin();
}
return;
}
configBindable.Value = skin.NewValue.ID.ToString();
});
}
private void skinsChanged(IRealmCollection<SkinInfo> sender, ChangeSet changes, Exception error)
@ -132,25 +110,7 @@ namespace osu.Game.Overlays.Settings.Sections
dropdownItems.Add(skin.ToLive(realm));
dropdownItems.Insert(protectedCount, random_skin_info);
Schedule(() =>
{
skinDropdown.Items = dropdownItems;
updateSelectedSkinFromConfig();
});
}
private void updateSelectedSkinFromConfig()
{
if (!skinDropdown.Items.Any())
return;
Live<SkinInfo> skin = null;
if (Guid.TryParse(configBindable.Value, out var configId))
skin = skinDropdown.Items.FirstOrDefault(s => s.ID == configId);
dropdownBindable.Value = skin ?? skinDropdown.Items.First();
Schedule(() => skinDropdown.Items = dropdownItems);
}
protected override void Dispose(bool isDisposing)

View File

@ -150,9 +150,9 @@ namespace osu.Game.Rulesets.Mods
if (comboBasedSize)
{
if (combo > 200)
if (combo >= 200)
size *= 0.8f;
else if (combo > 100)
else if (combo >= 100)
size *= 0.9f;
}

View File

@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000);
float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom);
float initialZoom = (float)(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom == 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom));
float minimumZoom = getZoomLevelForVisibleMilliseconds(10000);
float maximumZoom = getZoomLevelForVisibleMilliseconds(500);

View File

@ -87,6 +87,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (minimum > maximum)
throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be less than {nameof(maximum)} ({maximum})");
if (initial < minimum || initial > maximum)
throw new ArgumentException($"{nameof(initial)} ({initial}) must be between {nameof(minimum)} ({minimum}) and {nameof(maximum)} ({maximum})");
minZoom = minimum;
maxZoom = maximum;
CurrentZoom = zoomTarget = initial;

View File

@ -110,6 +110,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (Client != null)
{
Client.RoomUpdated -= invokeOnRoomUpdated;
Client.LoadRequested -= invokeOnRoomLoadRequested;
Client.UserLeft -= invokeUserLeft;
Client.UserKicked -= invokeUserKicked;
Client.UserJoined -= invokeUserJoined;

View File

@ -119,7 +119,9 @@ namespace osu.Game.Skinning
Realm.Run(r =>
{
// choose from only user skins, removing the current selection to ensure a new one is chosen.
var randomChoices = r.All<SkinInfo>().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray();
var randomChoices = r.All<SkinInfo>()
.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID)
.ToArray();
if (randomChoices.Length == 0)
{
@ -297,5 +299,21 @@ namespace osu.Game.Skinning
Delete(items.ToList(), silent);
});
}
public void SetSkinFromConfiguration(string guidString)
{
Live<SkinInfo> skinInfo = null;
if (Guid.TryParse(guidString, out var guid))
skinInfo = Query(s => s.ID == guid);
if (skinInfo == null)
{
if (guid == SkinInfo.CLASSIC_SKIN)
skinInfo = DefaultLegacySkin.SkinInfo;
}
CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.SkinInfo;
}
}
}

View File

@ -35,7 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.15.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.908.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.916.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
<PackageReference Include="Sentry" Version="3.20.1" />
<PackageReference Include="SharpCompress" Version="0.32.2" />

View File

@ -61,7 +61,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.908.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.916.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.831.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
@ -82,7 +82,7 @@
<PackageReference Include="DiffPlex" Version="1.7.1" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.908.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.916.1" />
<PackageReference Include="SharpCompress" Version="0.32.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />