1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:37:28 +08:00

Merge branch 'master' into argon-skin

This commit is contained in:
Dean Herbert 2022-09-21 13:00:16 +09:00 committed by GitHub
commit 4492a26b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 322 additions and 95 deletions

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

@ -44,6 +44,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
if (mods.Any(m => m is OsuModTouchDevice))
{
aimRating = Math.Pow(aimRating, 0.8);
flashlightRating = Math.Pow(flashlightRating, 0.8);
}
if (mods.Any(h => h is OsuModRelax))
{
aimRating *= 0.9;
@ -127,6 +133,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new OsuModTouchDevice(),
new OsuModDoubleTime(),
new OsuModHalfTime(),
new OsuModEasy(),

View File

@ -88,12 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
{
double rawAim = attributes.AimDifficulty;
if (score.Mods.Any(m => m is OsuModTouchDevice))
rawAim = Math.Pow(rawAim, 0.8);
double aimValue = Math.Pow(5.0 * Math.Max(1.0, rawAim / 0.0675) - 4.0, 3.0) / 100000.0;
double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
@ -233,12 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (!score.Mods.Any(h => h is OsuModFlashlight))
return 0.0;
double rawFlashlight = attributes.FlashlightDifficulty;
if (score.Mods.Any(m => m is OsuModTouchDevice))
rawFlashlight = Math.Pow(rawFlashlight, 0.8);
double flashlightValue = Math.Pow(rawFlashlight, 2.0) * 25.0;
double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0;
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (effectiveMissCount > 0)

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

@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
SetContents(skin =>
{
var implementation = skin is not TrianglesSkin
var implementation = skin is LegacySkin
? CreateLegacyImplementation()
: CreateDefaultImplementation();

View File

@ -264,13 +264,13 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestMutedNotificationMasterVolume()
{
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.IsDefault);
addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, () => audioManager.Volume.Value == 0.5);
}
[Test]
public void TestMutedNotificationTrackVolume()
{
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.IsDefault);
addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, () => audioManager.VolumeTrack.Value == 0.5);
}
[Test]

View File

@ -15,8 +15,10 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
@ -101,6 +103,37 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for last played to update", () => getLastPlayed() != null);
}
[Test]
public void TestModReferenceNotRetained()
{
AddStep("allow fail", () => allowFail = false);
Mod[] originalMods = { new OsuModDaycore { SpeedChange = { Value = 0.8 } } };
Mod[] playerMods = null!;
AddStep("load player with mods", () => LoadPlayer(originalMods));
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
AddStep("get mods at start of gameplay", () => playerMods = Player.Score.ScoreInfo.Mods.ToArray());
// Player creates new instance of mods during load.
AddAssert("player score has copied mods", () => playerMods.First(), () => Is.Not.SameAs(originalMods.First()));
AddAssert("player score has matching mods", () => playerMods.First(), () => Is.EqualTo(originalMods.First()));
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
// Player creates new instance of mods after gameplay to ensure any runtime references to drawables etc. are not retained.
AddAssert("results screen score has copied mods", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.Not.SameAs(playerMods.First()));
AddAssert("results screen score has matching", () => (Player.GetChildScreen() as ResultsScreen)?.Score.Mods.First(), () => Is.EqualTo(playerMods.First()));
AddUntilStep("score in database", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID) != null));
AddUntilStep("databased score has correct mods", () => Realm.Run(r => r.Find<ScoreInfo>(Player.Score.ScoreInfo.ID)).Mods.First(), () => Is.EqualTo(playerMods.First()));
}
[Test]
public void TestScoreStoredLocally()
{

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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -10,6 +11,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Updater;
@ -32,6 +34,8 @@ namespace osu.Game.Tests.Visual.UserInterface
[SetUp]
public void SetUp() => Schedule(() =>
{
InputManager.MoveMouseTo(Vector2.Zero);
TimeToCompleteProgress = 2000;
progressingNotifications.Clear();
@ -45,7 +49,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]
@ -228,6 +232,31 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("wait overlay not present", () => !notificationOverlay.IsPresent);
}
[Test]
public void TestProgressClick()
{
ProgressNotification notification = null!;
AddStep("add progress notification", () =>
{
notification = new ProgressNotification
{
Text = @"Uploading to BSS...",
CompletionText = "Uploaded to BSS!",
};
notificationOverlay.Post(notification);
progressingNotifications.Add(notification);
});
AddStep("hover over notification", () => InputManager.MoveMouseTo(notificationOverlay.ChildrenOfType<ProgressNotification>().Single()));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("not cancelled", () => notification.State == ProgressNotificationState.Active);
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddAssert("cancelled", () => notification.State == ProgressNotificationState.Cancelled);
}
[Test]
public void TestCompleteProgress()
{
@ -270,6 +299,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]
@ -297,7 +328,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
SimpleNotification notification = null!;
AddStep(@"post", () => notificationOverlay.Post(notification = new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" }));
AddUntilStep("check is toast", () => !notification.IsInToastTray);
AddUntilStep("check is toast", () => notification.IsInToastTray);
AddAssert("light is not visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 0);
AddUntilStep("wait for forward to overlay", () => !notification.IsInToastTray);
@ -422,6 +453,14 @@ namespace osu.Game.Tests.Visual.UserInterface
AddRepeatStep("send barrage", sendBarrage, 10);
}
[Test]
public void TestServerShuttingDownNotification()
{
AddStep("post with 5 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(5))));
AddStep("post with 30 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(30))));
AddStep("post with 6 hours", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromHours(6))));
}
protected override void Update()
{
base.Update();

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

@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer.Countdown;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Utils;
@ -26,6 +27,8 @@ namespace osu.Game.Online.Multiplayer
{
public abstract class MultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer
{
public Action<Notification>? PostNotification { protected get; set; }
/// <summary>
/// Invoked when any change occurs to the multiplayer room.
/// </summary>
@ -207,6 +210,8 @@ namespace osu.Game.Online.Multiplayer
updateLocalRoomSettings(joinedRoom.Settings);
postServerShuttingDownNotification();
OnRoomJoined();
}, cancellationSource.Token).ConfigureAwait(false);
}, cancellationSource.Token).ConfigureAwait(false);
@ -554,6 +559,14 @@ namespace osu.Game.Online.Multiplayer
{
case CountdownStartedEvent countdownStartedEvent:
Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown);
switch (countdownStartedEvent.Countdown)
{
case ServerShuttingDownCountdown:
postServerShuttingDownNotification();
break;
}
break;
case CountdownStoppedEvent countdownStoppedEvent:
@ -569,6 +582,16 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask;
}
private void postServerShuttingDownNotification()
{
ServerShuttingDownCountdown? countdown = room?.ActiveCountdowns.OfType<ServerShuttingDownCountdown>().FirstOrDefault();
if (countdown == null)
return;
PostNotification?.Invoke(new ServerShutdownNotification(countdown.TimeRemaining));
}
Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability)
{
Scheduler.Add(() =>

View File

@ -13,6 +13,7 @@ namespace osu.Game.Online.Multiplayer
[MessagePackObject]
[Union(0, typeof(MatchStartCountdown))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
[Union(1, typeof(ForceGameplayStartCountdown))]
[Union(2, typeof(ServerShuttingDownCountdown))]
public abstract class MultiplayerCountdown
{
/// <summary>

View File

@ -0,0 +1,62 @@
// 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 Humanizer.Localisation;
using osu.Framework.Allocation;
using osu.Framework.Threading;
using osu.Game.Overlays.Notifications;
using osu.Game.Utils;
namespace osu.Game.Online.Multiplayer
{
public class ServerShutdownNotification : SimpleNotification
{
private readonly DateTimeOffset endDate;
private ScheduledDelegate? updateDelegate;
public ServerShutdownNotification(TimeSpan duration)
{
endDate = DateTimeOffset.UtcNow + duration;
}
[BackgroundDependencyLoader]
private void load()
{
updateTime();
}
protected override void LoadComplete()
{
base.LoadComplete();
updateDelegate = Scheduler.Add(updateTimeWithReschedule);
}
private void updateTimeWithReschedule()
{
updateTime();
// The remaining time on a countdown may be at a fractional portion between two seconds.
// We want to align certain audio/visual cues to the point at which integer seconds change.
// To do so, we schedule to the next whole second. Note that scheduler invocation isn't
// guaranteed to be accurate, so this may still occur slightly late, but even in such a case
// the next invocation will be roughly correct.
double timeToNextSecond = endDate.Subtract(DateTimeOffset.UtcNow).TotalMilliseconds % 1000;
updateDelegate = Scheduler.AddDelayed(updateTimeWithReschedule, timeToNextSecond);
}
private void updateTime()
{
TimeSpan remaining = endDate.Subtract(DateTimeOffset.Now);
if (remaining.TotalSeconds <= 5)
{
updateDelegate?.Cancel();
Text = "The multiplayer server will be right back...";
}
else
Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(remaining, precision: 3, minUnit: TimeUnit.Second)}.";
}
}
}

View File

@ -0,0 +1,15 @@
// 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 MessagePack;
namespace osu.Game.Online.Multiplayer
{
/// <summary>
/// A countdown that indicates the current multiplayer server is shutting down.
/// </summary>
[MessagePackObject]
public class ServerShuttingDownCountdown : MultiplayerCountdown
{
}
}

View File

@ -28,7 +28,8 @@ namespace osu.Game.Online
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
(typeof(TeamVersusUserState), typeof(MatchUserState)),
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
(typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown))
(typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown)),
(typeof(ServerShuttingDownCountdown), typeof(MultiplayerCountdown)),
};
}
}

View File

@ -716,6 +716,8 @@ namespace osu.Game
ScoreManager.PostNotification = n => Notifications.Post(n);
ScoreManager.PresentImport = items => PresentScore(items.First().Value);
MultiplayerClient.PostNotification = n => Notifications.Post(n);
// make config aware of how to lookup skins for on-screen display purposes.
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown";

View File

@ -179,7 +179,7 @@ namespace osu.Game
private SpectatorClient spectatorClient;
private MultiplayerClient multiplayerClient;
protected MultiplayerClient MultiplayerClient { get; private set; }
private MetadataClient metadataClient;
@ -284,7 +284,7 @@ namespace osu.Game
// TODO: OsuGame or OsuGameBase?
dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage));
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));
dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints));
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient));
@ -329,7 +329,7 @@ namespace osu.Game
AddInternal(apiAccess);
AddInternal(spectatorClient);
AddInternal(multiplayerClient);
AddInternal(MultiplayerClient);
AddInternal(metadataClient);
AddInternal(rulesetConfigCache);

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;
@ -226,8 +229,8 @@ namespace osu.Game.Overlays.Notifications
protected override bool OnClick(ClickEvent e)
{
// Clicking with anything but left button should dismiss but not perform the activation action.
if (e.Button == MouseButton.Left)
Activated?.Invoke();
if (e.Button == MouseButton.Left && Activated?.Invoke() == false)
return true;
Close(false);
return true;
@ -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

@ -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

@ -137,6 +137,11 @@ namespace osu.Game.Scoring
clone.Statistics = new Dictionary<HitResult, int>(clone.Statistics);
clone.MaximumStatistics = new Dictionary<HitResult, int>(clone.MaximumStatistics);
// Ensure we have fresh mods to avoid any references (ie. after gameplay).
clone.clearAllMods();
clone.ModsJson = ModsJson;
clone.RealmUser = new RealmUser
{
OnlineID = RealmUser.OnlineID,

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

@ -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.
#nullable disable
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -25,9 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
private const double fade_time = 50;
private SpriteIcon icon;
private OsuSpriteText text;
private ProgressBar progressBar;
private SpriteIcon icon = null!;
private OsuSpriteText text = null!;
private ProgressBar progressBar = null!;
public StateDisplay()
{
@ -86,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
};
}
private OsuColour colours;
private OsuColour colours = null!;
public void UpdateStatus(MultiplayerUserState state, BeatmapAvailability availability)
{
@ -164,10 +161,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
break;
case DownloadState.Downloading:
Debug.Assert(availability.DownloadProgress != null);
progressBar.FadeIn(fade_time);
progressBar.CurrentTime = availability.DownloadProgress.Value;
progressBar.CurrentTime = availability.DownloadProgress ?? 0;
text.Text = "downloading map";
icon.Icon = FontAwesome.Solid.ArrowAltCircleDown;

View File

@ -47,10 +47,7 @@ namespace osu.Game.Screens.Play.HUD
if (clock != null)
gameplayClock = clock;
// Lock height so changes in text autosize (if character height changes)
// don't cause parent invalidation.
Height = 14;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new Container

View File

@ -502,7 +502,7 @@ namespace osu.Game.Screens.Play
private int restartCount;
private const double volume_requirement = 0.05;
private const double volume_requirement = 0.01;
private void showMuteWarningIfNeeded()
{
@ -539,10 +539,11 @@ namespace osu.Game.Screens.Play
volumeOverlay.IsMuted.Value = false;
// Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes.
// Note that we only restore halfway to ensure the user isn't suddenly overloaded by unexpectedly high volume.
if (audioManager.Volume.Value <= volume_requirement)
audioManager.Volume.SetDefault();
audioManager.Volume.Value = 0.5f;
if (audioManager.VolumeTrack.Value <= volume_requirement)
audioManager.VolumeTrack.SetDefault();
audioManager.VolumeTrack.Value = 0.5f;
return true;
};

View File

@ -15,7 +15,6 @@ using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Allocation;
using osu.Framework.Layout;
using osu.Framework.Threading;
namespace osu.Game.Screens.Play
@ -24,11 +23,6 @@ namespace osu.Game.Screens.Play
{
private BufferedContainer<Column> columns;
public SquareGraph()
{
AddLayout(layout);
}
public int ColumnCount => columns?.Children.Count ?? 0;
private int progress;
@ -57,7 +51,7 @@ namespace osu.Game.Screens.Play
if (value == values) return;
values = value;
layout.Invalidate();
graphNeedsUpdate = true;
}
}
@ -75,21 +69,25 @@ namespace osu.Game.Screens.Play
}
}
private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize);
private ScheduledDelegate scheduledCreate;
private bool graphNeedsUpdate;
private Vector2 previousDrawSize;
protected override void Update()
{
base.Update();
if (values != null && !layout.IsValid)
if (graphNeedsUpdate || (values != null && DrawSize != previousDrawSize))
{
columns?.FadeOut(500, Easing.OutQuint).Expire();
scheduledCreate?.Cancel();
scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500);
layout.Validate();
previousDrawSize = DrawSize;
graphNeedsUpdate = false;
}
}

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.IO;
@ -46,20 +45,18 @@ namespace osu.Game.Skinning
var type = string.IsNullOrEmpty(InstantiationInfo)
// handle the case of skins imported before InstantiationInfo was added.
? typeof(LegacySkin)
: Type.GetType(InstantiationInfo).AsNonNull();
: Type.GetType(InstantiationInfo);
try
if (type == null)
{
return (Skin)Activator.CreateInstance(type, this, resources);
}
catch
{
// Since the class was renamed from "DefaultSkin" to "TrianglesSkin", the instantiation would fail
// Since the class was renamed from "DefaultSkin" to "TrianglesSkin", the type retrieval would fail
// for user modified skins. This aims to amicably handle that.
// If we ever add more default skins in the future this will need some kind of proper migration rather than
// a single catch.
// a single fallback.
return new TrianglesSkin(this, resources);
}
return (Skin)Activator.CreateInstance(type, this, resources);
}
public IList<RealmNamedFileUsage> Files { get; } = null!;

View File

@ -7,7 +7,6 @@ using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets;
@ -57,7 +56,9 @@ namespace osu.Game.Tests.Visual
protected virtual bool Autoplay => false;
protected void LoadPlayer()
protected void LoadPlayer() => LoadPlayer(Array.Empty<Mod>());
protected void LoadPlayer(Mod[] mods)
{
var ruleset = CreatePlayerRuleset();
Ruleset.Value = ruleset.RulesetInfo;
@ -65,20 +66,21 @@ namespace osu.Game.Tests.Visual
var beatmap = CreateBeatmap(ruleset.RulesetInfo);
Beatmap.Value = CreateWorkingBeatmap(beatmap);
SelectedMods.Value = Array.Empty<Mod>();
SelectedMods.Value = mods;
if (!AllowFail)
{
var noFailMod = ruleset.CreateMod<ModNoFail>();
if (noFailMod != null)
SelectedMods.Value = new[] { noFailMod };
SelectedMods.Value = SelectedMods.Value.Append(noFailMod).ToArray();
}
if (Autoplay)
{
var mod = ruleset.GetAutoplayMod();
if (mod != null)
SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray();
SelectedMods.Value = SelectedMods.Value.Append(mod).ToArray();
}
Player = CreatePlayer(ruleset);

View File

@ -4,6 +4,7 @@
using System;
using System.Globalization;
using Humanizer;
using Humanizer.Localisation;
namespace osu.Game.Utils
{
@ -26,5 +27,27 @@ namespace osu.Game.Utils
return input.Humanize(culture: new CultureInfo("en-US"));
}
}
/// <summary>
/// Turns the current or provided timespan into a human readable sentence
/// </summary>
/// <param name="input">The date to be humanized</param>
/// <param name="precision">The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned</param>
/// <param name="maxUnit">The maximum unit of time to output. The default value is <see cref="TimeUnit.Week"/>. The time units <see cref="TimeUnit.Month"/> and <see cref="TimeUnit.Year"/> will give approximations for time spans bigger 30 days by calculating with 365.2425 days a year and 30.4369 days a month.</param>
/// <param name="minUnit">The minimum unit of time to output.</param>
/// <param name="toWords">Uses words instead of numbers if true. E.g. one day.</param>
/// <returns>distance of time in words</returns>
public static string Humanize(TimeSpan input, int precision = 1, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond, bool toWords = false)
{
// this works around https://github.com/xamarin/xamarin-android/issues/2012 and https://github.com/Humanizr/Humanizer/issues/690#issuecomment-368536282
try
{
return input.Humanize(precision: precision, maxUnit: maxUnit, minUnit: minUnit);
}
catch (ArgumentException)
{
return input.Humanize(culture: new CultureInfo("en-US"), precision: precision, maxUnit: maxUnit, minUnit: minUnit);
}
}
}
}