Merge branch 'master' into diffcalc/skill-mods

This commit is contained in:
Dan Balasescu
2021-03-04 13:06:26 +09:00
committed by GitHub
157 changed files with 1392 additions and 624 deletions

View File

@ -69,8 +69,8 @@ namespace osu.Game.Beatmaps
/// </summary>
/// <param name="beatmapInfo">The <see cref="BeatmapInfo"/> to get the difficulty of.</param>
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available.</returns>
public IBindable<StarDifficulty> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated).</returns>
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
{
var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken);
@ -90,9 +90,9 @@ namespace osu.Game.Beatmaps
/// <param name="rulesetInfo">The <see cref="RulesetInfo"/> to get the difficulty with. If <c>null</c>, the <paramref name="beatmapInfo"/>'s ruleset is used.</param>
/// <param name="mods">The <see cref="Mod"/>s to get the difficulty with. If <c>null</c>, no mods will be assumed.</param>
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops updating the star difficulty for the given <see cref="BeatmapInfo"/>.</param>
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available.</returns>
public IBindable<StarDifficulty> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods,
CancellationToken cancellationToken = default)
/// <returns>A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state.</returns>
public IBindable<StarDifficulty?> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable<Mod> mods,
CancellationToken cancellationToken = default)
=> createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken);
/// <summary>
@ -313,7 +313,7 @@ namespace osu.Game.Beatmaps
}
}
private class BindableStarDifficulty : Bindable<StarDifficulty>
private class BindableStarDifficulty : Bindable<StarDifficulty?>
{
public readonly BeatmapInfo Beatmap;
public readonly CancellationToken CancellationToken;

View File

@ -20,6 +20,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Statistics;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
@ -311,6 +312,9 @@ namespace osu.Game.Beatmaps
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
// best effort; may be higher than expected.
GlobalStatistics.Get<int>(nameof(Beatmaps), $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count();
return working;
}
}

View File

@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables
this.mods = mods;
}
private IBindable<StarDifficulty> localStarDifficulty;
private IBindable<StarDifficulty?> localStarDifficulty;
[BackgroundDependencyLoader]
private void load()
@ -160,7 +160,11 @@ namespace osu.Game.Beatmaps.Drawables
localStarDifficulty = ruleset != null
? difficultyCache.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token)
: difficultyCache.GetBindableDifficulty(beatmap, difficultyCancellation.Token);
localStarDifficulty.BindValueChanged(difficulty => StarDifficulty.Value = difficulty.NewValue);
localStarDifficulty.BindValueChanged(d =>
{
if (d.NewValue is StarDifficulty diff)
StarDifficulty.Value = diff;
});
}
protected override void Dispose(bool isDisposing)

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Beatmaps.Timing;
@ -348,8 +349,8 @@ namespace osu.Game.Beatmaps.Formats
if (split.Length >= 8)
{
LegacyEffectFlags effectFlags = (LegacyEffectFlags)Parsing.ParseInt(split[7]);
kiaiMode = effectFlags.HasFlag(LegacyEffectFlags.Kiai);
omitFirstBarSignature = effectFlags.HasFlag(LegacyEffectFlags.OmitFirstBarLine);
kiaiMode = effectFlags.HasFlagFast(LegacyEffectFlags.Kiai);
omitFirstBarSignature = effectFlags.HasFlagFast(LegacyEffectFlags.OmitFirstBarLine);
}
string stringSampleSet = sampleSet.ToString().ToLowerInvariant();

View File

@ -12,7 +12,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Statistics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -34,8 +33,6 @@ namespace osu.Game.Beatmaps
protected AudioManager AudioManager { get; }
private static readonly GlobalStatistic<int> total_count = GlobalStatistics.Get<int>(nameof(Beatmaps), $"Total {nameof(WorkingBeatmap)}s");
protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager)
{
AudioManager = audioManager;
@ -47,8 +44,6 @@ namespace osu.Game.Beatmaps
waveform = new RecyclableLazy<Waveform>(GetWaveform);
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
skin = new RecyclableLazy<ISkin>(GetSkin);
total_count.Value++;
}
protected virtual Track GetVirtualTrack(double emptyLength = 0)
@ -331,11 +326,6 @@ namespace osu.Game.Beatmaps
protected virtual ISkin GetSkin() => new DefaultSkin();
private readonly RecyclableLazy<ISkin> skin;
~WorkingBeatmap()
{
total_count.Value--;
}
public class RecyclableLazy<T>
{
private Lazy<T> lazy;

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@ -121,7 +122,7 @@ namespace osu.Game.Collections
Current.TriggerChange();
}
protected override string GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value;
protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value;
protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d =>
{
@ -139,7 +140,7 @@ namespace osu.Game.Collections
public readonly Bindable<CollectionFilterMenuItem> SelectedItem = new Bindable<CollectionFilterMenuItem>();
private readonly Bindable<string> collectionName = new Bindable<string>();
protected override string Label
protected override LocalisableString Label
{
get => base.Label;
set { } // See updateText().

View File

@ -36,7 +36,19 @@ namespace osu.Game.Collections
}
public bool Equals(CollectionFilterMenuItem other)
=> other != null && CollectionName.Value == other.CollectionName.Value;
{
if (other == null)
return false;
// collections may have the same name, so compare first on reference equality.
// this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager.
if (Collection != null)
return Collection == other.Collection;
// fallback to name-based comparison.
// this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below).
return CollectionName.Value == other.CollectionName.Value;
}
public override int GetHashCode() => CollectionName.Value.GetHashCode();
}

View File

@ -138,10 +138,10 @@ namespace osu.Game.Collections
PostNotification?.Invoke(notification);
var collection = readCollections(stream, notification);
await importCollections(collection);
var collections = readCollections(stream, notification);
await importCollections(collections);
notification.CompletionText = $"Imported {collection.Count} collections";
notification.CompletionText = $"Imported {collections.Count} collections";
notification.State = ProgressNotificationState.Completed;
}
@ -155,7 +155,7 @@ namespace osu.Game.Collections
{
foreach (var newCol in newCollections)
{
var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
var existing = Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value);
if (existing == null)
Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });

View File

@ -8,6 +8,7 @@ using System.Reflection;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Overlays.Settings;
namespace osu.Game.Configuration
@ -22,9 +23,9 @@ namespace osu.Game.Configuration
/// </remarks>
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Property)]
public class SettingSourceAttribute : Attribute
public class SettingSourceAttribute : Attribute, IComparable<SettingSourceAttribute>
{
public string Label { get; }
public LocalisableString Label { get; }
public string Description { get; }
@ -41,6 +42,21 @@ namespace osu.Game.Configuration
{
OrderPosition = orderPosition;
}
public int CompareTo(SettingSourceAttribute other)
{
if (OrderPosition == other.OrderPosition)
return 0;
// unordered items come last (are greater than any ordered items).
if (OrderPosition == null)
return 1;
if (other.OrderPosition == null)
return -1;
// ordered items are sorted by the order value.
return OrderPosition.Value.CompareTo(other.OrderPosition);
}
}
public static class SettingSourceExtensions
@ -136,14 +152,9 @@ namespace osu.Game.Configuration
}
}
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
{
var original = obj.GetSettingsSourceProperties();
var orderedRelative = original.Where(attr => attr.Item1.OrderPosition != null).OrderBy(attr => attr.Item1.OrderPosition);
var unordered = original.Except(orderedRelative);
return orderedRelative.Concat(unordered);
}
public static ICollection<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
=> obj.GetSettingsSourceProperties()
.OrderBy(attr => attr.Item1)
.ToArray();
}
}

View File

@ -141,6 +141,13 @@ namespace osu.Game.Database
protected async Task<IEnumerable<TModel>> Import(ProgressNotification notification, params ImportTask[] tasks)
{
if (tasks.Length == 0)
{
notification.CompletionText = $"No {HumanisedModelName}s were found to import!";
notification.State = ProgressNotificationState.Completed;
return Enumerable.Empty<TModel>();
}
notification.Progress = 0;
notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising...";

View File

@ -54,10 +54,5 @@ namespace osu.Game.Database
Dispose(true);
GC.SuppressFinalize(this);
}
~DatabaseWriteUsage()
{
Dispose(false);
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osuTK;
namespace osu.Game.Graphics.Sprites
@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Sprites
{
private readonly OsuSpriteText spriteText, blurredText;
public string Text
public LocalisableString Text
{
get => spriteText.Text;
set => blurredText.Text = spriteText.Text = value;

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
namespace osu.Game.Graphics.UserInterface
{
@ -24,11 +25,11 @@ namespace osu.Game.Graphics.UserInterface
set
{
direction = value;
base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal;
base.Direction = direction.HasFlagFast(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal;
foreach (var bar in Children)
{
bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1);
bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1);
bar.Direction = direction;
}
}
@ -56,14 +57,14 @@ namespace osu.Game.Graphics.UserInterface
if (bar.Bar != null)
{
bar.Bar.Length = length;
bar.Bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1);
bar.Bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1);
}
else
{
Add(new Bar
{
RelativeSizeAxes = Axes.Both,
Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1),
Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1),
Length = length,
Direction = Direction,
});

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface
protected class TextContainer : Container, IHasText
{
public string Text
public LocalisableString Text
{
get => NormalText.Text;
set

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@ -21,9 +22,9 @@ namespace osu.Game.Graphics.UserInterface
/// </summary>
public class OsuButton : Button
{
public string Text
public LocalisableString Text
{
get => SpriteText?.Text;
get => SpriteText?.Text ?? default;
set
{
if (SpriteText != null)

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
@ -168,7 +169,7 @@ namespace osu.Game.Graphics.UserInterface
protected new class Content : FillFlowContainer, IHasText
{
public string Text
public LocalisableString Text
{
get => Label.Text;
set => Label.Text = value;
@ -215,7 +216,7 @@ namespace osu.Game.Graphics.UserInterface
{
protected readonly SpriteText Text;
protected override string Label
protected override LocalisableString Label
{
get => Text.Text;
set => Text.Text = value;

View File

@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@ -35,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
}
}
public string Text
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;

View File

@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK;
using System.Collections.Generic;
using osu.Framework.Localisation;
namespace osu.Game.Graphics.UserInterface
{
@ -18,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface
{
private const int duration = 200;
public string Text
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;

View File

@ -27,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface
});
}
public virtual IEnumerable<string> FilterTerms => new[] { Text };
public virtual IEnumerable<string> FilterTerms => new[] { Text.ToString() };
public bool MatchingFilter
{

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Framework.Audio.Track;
using System;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
@ -56,15 +57,15 @@ namespace osu.Game.Graphics.UserInterface
set
{
base.Origin = value;
c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
c1.Origin = c1.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight;
c2.Origin = c2.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft;
X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
X = value.HasFlagFast(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0;
Remove(c1);
Remove(c2);
c1.Depth = value.HasFlag(Anchor.x2) ? 0 : 1;
c2.Depth = value.HasFlag(Anchor.x2) ? 1 : 0;
c1.Depth = value.HasFlagFast(Anchor.x2) ? 0 : 1;
c2.Depth = value.HasFlagFast(Anchor.x2) ? 1 : 0;
Add(c1);
Add(c2);
}

View File

@ -10,6 +10,7 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using osu.Framework;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Extensions.ObjectExtensions;
@ -246,7 +247,14 @@ namespace osu.Game.Online.API
this.password = password;
}
public IHubClientConnector GetHubConnector(string clientName, string endpoint) => new HubClientConnector(clientName, endpoint, this, versionHash);
public IHubClientConnector GetHubConnector(string clientName, string endpoint)
{
// disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805.
if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS)
return null;
return new HubClientConnector(clientName, endpoint, this, versionHash);
}
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
{
@ -373,7 +381,13 @@ namespace osu.Game.Online.API
public void Queue(APIRequest request)
{
lock (queue) queue.Enqueue(request);
lock (queue)
{
if (state.Value == APIState.Offline)
return;
queue.Enqueue(request);
}
}
private void flushQueue(bool failOldRequests = true)
@ -394,8 +408,6 @@ namespace osu.Game.Online.API
public void Logout()
{
flushQueue();
password = null;
authentication.Clear();
@ -407,6 +419,7 @@ namespace osu.Game.Online.API
});
state.Value = APIState.Offline;
flushQueue();
}
private static User createGuestUser() => new GuestUser();

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 osu.Framework.IO.Network;
using osu.Game.Beatmaps;
namespace osu.Game.Online.API.Requests
@ -15,6 +16,13 @@ namespace osu.Game.Online.API.Requests
this.noVideo = noVideo;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Timeout = 60000;
return req;
}
protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}";
}
}

View File

@ -152,7 +152,7 @@ namespace osu.Game.Online.Chat
createNewPrivateMessageRequest.Failure += exception =>
{
Logger.Error(exception, "Posting message failed.");
handlePostException(exception);
target.ReplaceMessage(message, null);
dequeueAndRun();
};
@ -171,7 +171,7 @@ namespace osu.Game.Online.Chat
req.Failure += exception =>
{
Logger.Error(exception, "Posting message failed.");
handlePostException(exception);
target.ReplaceMessage(message, null);
dequeueAndRun();
};
@ -184,6 +184,14 @@ namespace osu.Game.Online.Chat
dequeueAndRun();
}
private static void handlePostException(Exception exception)
{
if (exception is APIException apiException)
Logger.Log(apiException.Message, level: LogLevel.Important);
else
Logger.Error(exception, "Posting message failed.");
}
/// <summary>
/// Posts a command locally. Commands like /help will result in a help message written in the current channel.
/// </summary>

View File

@ -361,14 +361,6 @@ namespace osu.Game
PerformFromScreen(screen =>
{
// we might already be at song select, so a check is required before performing the load to solo.
if (screen is MainMenu)
menuScreen.LoadToSolo();
// we might even already be at the song
if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && (difficultyCriteria?.Invoke(Beatmap.Value.BeatmapInfo) ?? true))
return;
// Find beatmaps that match our predicate.
var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList();
@ -381,9 +373,16 @@ namespace osu.Game
?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value))
?? beatmaps.First();
Ruleset.Value = selection.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
}, validScreens: new[] { typeof(SongSelect) });
if (screen is IHandlePresentBeatmap presentableScreen)
{
presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset);
}
else
{
Ruleset.Value = selection.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
}
}, validScreens: new[] { typeof(SongSelect), typeof(IHandlePresentBeatmap) });
}
/// <summary>

View File

@ -84,14 +84,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)),
Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)),
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
},

View File

@ -107,14 +107,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)),
Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
new OsuSpriteText
{
Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)),
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
}

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -96,7 +97,7 @@ namespace osu.Game.Overlays.BeatmapSet
public string TooltipText { get; }
public string Value
public LocalisableString Value
{
get => value.Text;
set => this.value.Text = value;

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@ -204,7 +203,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
this.text = text;
}
public LocalisedString Text
public string Text
{
set => text.Text = value;
}

View File

@ -4,19 +4,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osuTK;
namespace osu.Game.Overlays.Chat.Selection
{
public class ChannelSection : Container, IHasFilterableChildren
{
private readonly OsuSpriteText header;
public readonly FillFlowContainer<ChannelListItem> ChannelFlow;
public IEnumerable<IFilterable> FilterableChildren => ChannelFlow.Children;
@ -29,12 +27,6 @@ namespace osu.Game.Overlays.Chat.Selection
public bool FilteringActive { get; set; }
public string Header
{
get => header.Text;
set => header.Text = value.ToUpperInvariant();
}
public IEnumerable<Channel> Channels
{
set => ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c));
@ -47,9 +39,10 @@ namespace osu.Game.Overlays.Chat.Selection
Children = new Drawable[]
{
header = new OsuSpriteText
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
Text = "All Channels".ToUpperInvariant()
},
ChannelFlow = new FillFlowContainer<ChannelListItem>
{

View File

@ -131,11 +131,7 @@ namespace osu.Game.Overlays.Chat.Selection
{
sectionsFlow.ChildrenEnumerable = new[]
{
new ChannelSection
{
Header = "All Channels",
Channels = channels,
},
new ChannelSection { Channels = channels, },
};
foreach (ChannelSection s in sectionsFlow.Children)

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Comments.Buttons
{
public abstract class CommentRepliesButton : CompositeDrawable
{
protected string Text
protected LocalisableString Text
{
get => text.Text;
set => text.Text = value;

View File

@ -0,0 +1,42 @@
// 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.Graphics.Sprites;
namespace osu.Game.Overlays.Dialog
{
/// <summary>
/// A dialog which confirms a user action.
/// </summary>
public class ConfirmDialog : PopupDialog
{
/// <summary>
/// Construct a new confirmation dialog.
/// </summary>
/// <param name="message">The description of the action to be displayed to the user.</param>
/// <param name="onConfirm">An action to perform on confirmation.</param>
/// <param name="onCancel">An optional action to perform on cancel.</param>
public ConfirmDialog(string message, Action onConfirm, Action onCancel = null)
{
HeaderText = message;
BodyText = "Last chance to turn back";
Icon = FontAwesome.Solid.ExclamationTriangle;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"Yes",
Action = onConfirm
},
new PopupDialogCancelButton
{
Text = @"Cancel",
Action = onCancel
},
};
}
}
}

View File

@ -51,7 +51,7 @@ namespace osu.Game.Overlays.KeyBinding
private FillFlowContainer cancelAndClearButtons;
private FillFlowContainer<KeyButton> buttons;
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text);
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
public KeyBindingRow(object action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings)
{

View File

@ -23,13 +23,15 @@ namespace osu.Game.Overlays.Mods
public FillFlowContainer<ModButtonEmpty> ButtonsContainer { get; }
protected IReadOnlyList<ModButton> Buttons { get; private set; } = Array.Empty<ModButton>();
public Action<Mod> Action;
public Key[] ToggleKeys;
public readonly ModType ModType;
public IEnumerable<Mod> SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null);
public IEnumerable<Mod> SelectedMods => Buttons.Select(b => b.SelectedMod).Where(m => m != null);
private CancellationTokenSource modsLoadCts;
@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Mods
ButtonsContainer.ChildrenEnumerable = c;
}, (modsLoadCts = new CancellationTokenSource()).Token);
buttons = modContainers.OfType<ModButton>().ToArray();
Buttons = modContainers.OfType<ModButton>().ToArray();
header.FadeIn(200);
this.FadeIn(200);
@ -88,8 +90,6 @@ namespace osu.Game.Overlays.Mods
{
}
private ModButton[] buttons = Array.Empty<ModButton>();
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.ControlPressed) return false;
@ -97,8 +97,8 @@ namespace osu.Game.Overlays.Mods
if (ToggleKeys != null)
{
var index = Array.IndexOf(ToggleKeys, e.Key);
if (index > -1 && index < buttons.Length)
buttons[index].SelectNext(e.ShiftPressed ? -1 : 1);
if (index > -1 && index < Buttons.Count)
Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1);
}
return base.OnKeyDown(e);
@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Mods
{
pendingSelectionOperations.Clear();
foreach (var button in buttons.Where(b => !b.Selected))
foreach (var button in Buttons.Where(b => !b.Selected))
pendingSelectionOperations.Enqueue(() => button.SelectAt(0));
}
@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Mods
public void DeselectAll()
{
pendingSelectionOperations.Clear();
DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
DeselectTypes(Buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
}
/// <summary>
@ -161,7 +161,7 @@ namespace osu.Game.Overlays.Mods
/// <param name="immediate">Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow.</param>
public void DeselectTypes(IEnumerable<Type> modTypes, bool immediate = false)
{
foreach (var button in buttons)
foreach (var button in Buttons)
{
if (button.SelectedMod == null) continue;
@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Mods
/// <param name="newSelectedMods">The new list of selected mods to select.</param>
public void UpdateSelectedButtons(IReadOnlyList<Mod> newSelectedMods)
{
foreach (var button in buttons)
foreach (var button in Buttons)
updateButtonSelection(button, newSelectedMods);
}

View File

@ -456,6 +456,7 @@ namespace osu.Game.Overlays.Mods
}
updateSelectedButtons();
OnAvailableModsChanged();
}
/// <summary>
@ -533,6 +534,13 @@ namespace osu.Game.Overlays.Mods
private void playSelectedSound() => sampleOn?.Play();
private void playDeselectedSound() => sampleOff?.Play();
/// <summary>
/// Invoked after <see cref="availableMods"/> has changed.
/// </summary>
protected virtual void OnAvailableModsChanged()
{
}
/// <summary>
/// Invoked when a new <see cref="Mod"/> has been selected.
/// </summary>

View File

@ -48,8 +48,8 @@ namespace osu.Game.Overlays.Music
artistColour = colours.Gray9;
HandleColour = colours.Gray5;
title = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title)));
artist = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist)));
title = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title));
artist = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist));
}
protected override void LoadComplete()

View File

@ -8,10 +8,11 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Notifications
{
@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Notifications
public NotificationSection(string title, string clearButtonText)
{
this.clearButtonText = clearButtonText;
this.clearButtonText = clearButtonText.ToUpperInvariant();
titleText = title;
}
@ -121,7 +122,20 @@ namespace osu.Game.Overlays.Notifications
{
base.Update();
countDrawable.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString();
countDrawable.Text = getVisibleCount().ToString();
}
private int getVisibleCount()
{
int count = 0;
foreach (var c in notifications)
{
if (c.Alpha > 0.99f)
count++;
}
return count;
}
private class ClearAllButton : OsuClickableContainer
@ -138,10 +152,10 @@ namespace osu.Game.Overlays.Notifications
};
}
public string Text
public LocalisableString Text
{
get => text.Text;
set => text.Text = value.ToUpperInvariant();
set => text.Text = value;
}
}

View File

@ -293,8 +293,8 @@ namespace osu.Game.Overlays
else
{
BeatmapMetadata metadata = beatmap.Metadata;
title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title);
artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
}
});

View File

@ -17,6 +17,7 @@ using osu.Game.Overlays.Comments;
using JetBrains.Annotations;
using System;
using osu.Framework.Extensions;
using osu.Framework.Localisation;
namespace osu.Game.Overlays
{
@ -30,7 +31,7 @@ namespace osu.Game.Overlays
set => current.Current = value;
}
public string Title
public LocalisableString Title
{
get => text.Text;
set => text.Text = value;

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
@ -13,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
namespace osu.Game.Overlays.Profile.Sections.Historical
{
@ -129,14 +129,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
{
new OsuSpriteText
{
Text = new LocalisedString((
Text = new RomanisableString(
$"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ",
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")),
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "),
Font = OsuFont.GetFont(weight: FontWeight.Bold)
},
new OsuSpriteText
{
Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)),
Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Regular)
},
};

View File

@ -256,16 +256,16 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = new LocalisedString((
Text = new RomanisableString(
$"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} ",
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} ")),
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} "),
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)),
Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
Font = OsuFont.GetFont(size: 12, italics: true)
},
};

View File

@ -6,6 +6,7 @@ using osu.Framework.Audio;
using osu.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings.Sections.Audio
@ -76,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
private class AudioDeviceDropdownControl : DropdownControl
{
protected override string GenerateItemText(string item)
protected override LocalisableString GenerateItemText(string item)
=> string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item);
}
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
@ -234,7 +235,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private class ResolutionDropdownControl : DropdownControl
{
protected override string GenerateItemText(Size item)
protected override LocalisableString GenerateItemText(Size item)
{
if (item == new Size(9999, 9999))
return "Default";

View File

@ -17,7 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
protected override string Header => "Mouse";
private readonly BindableBool rawInputToggle = new BindableBool();
private Bindable<double> sensitivityBindable = new BindableDouble();
private Bindable<double> configSensitivity;
private Bindable<double> localSensitivity;
private Bindable<string> ignoredInputHandlers;
private Bindable<WindowMode> windowMode;
@ -26,12 +30,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input
[BackgroundDependencyLoader]
private void load(OsuConfigManager osuConfig, FrameworkConfigManager config)
{
var configSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity);
// use local bindable to avoid changing enabled state of game host's bindable.
sensitivityBindable = configSensitivity.GetUnboundCopy();
configSensitivity.BindValueChanged(val => sensitivityBindable.Value = val.NewValue);
sensitivityBindable.BindValueChanged(val => configSensitivity.Value = val.NewValue);
configSensitivity = config.GetBindable<double>(FrameworkSetting.CursorSensitivity);
localSensitivity = configSensitivity.GetUnboundCopy();
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
ignoredInputHandlers = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
Children = new Drawable[]
{
@ -43,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
new SensitivitySetting
{
LabelText = "Cursor sensitivity",
Current = sensitivityBindable
Current = localSensitivity
},
new SettingsCheckbox
{
@ -66,14 +70,35 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons)
},
};
}
windowMode = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode);
windowMode.BindValueChanged(mode => confineMouseModeSetting.Alpha = mode.NewValue == WindowMode.Fullscreen ? 0 : 1, true);
protected override void LoadComplete()
{
base.LoadComplete();
configSensitivity.BindValueChanged(val => localSensitivity.Value = val.NewValue, true);
localSensitivity.BindValueChanged(val => configSensitivity.Value = val.NewValue);
windowMode.BindValueChanged(mode =>
{
var isFullscreen = mode.NewValue == WindowMode.Fullscreen;
if (isFullscreen)
{
confineMouseModeSetting.Current.Disabled = true;
confineMouseModeSetting.TooltipText = "Not applicable in full screen mode";
}
else
{
confineMouseModeSetting.Current.Disabled = false;
confineMouseModeSetting.TooltipText = string.Empty;
}
}, true);
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{
rawInputToggle.Disabled = true;
sensitivityBindable.Disabled = true;
localSensitivity.Disabled = true;
}
else
{
@ -86,12 +111,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler;
};
ignoredInputHandlers = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
ignoredInputHandlers.ValueChanged += handler =>
{
bool raw = !handler.NewValue.Contains("Raw");
rawInputToggle.Value = raw;
sensitivityBindable.Disabled = !raw;
localSensitivity.Disabled = !raw;
};
ignoredInputHandlers.TriggerChange();

View File

@ -8,6 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
@ -178,7 +179,7 @@ namespace osu.Game.Overlays.Settings.Sections
private class SkinDropdownControl : DropdownControl
{
protected override string GenerateItemText(SkinInfo item) => item.ToString();
protected override LocalisableString GenerateItemText(SkinInfo item) => item.ToString();
}
}

View File

@ -2,20 +2,22 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
{
public class SettingsCheckbox : SettingsItem<bool>
{
private string labelText;
private LocalisableString labelText;
protected override Drawable CreateControl() => new OsuCheckbox();
public override string LabelText
public override LocalisableString LabelText
{
get => labelText;
set => ((OsuCheckbox)Control).LabelText = labelText = value;
// checkbox doesn't properly support localisation yet.
set => ((OsuCheckbox)Control).LabelText = (labelText = value).ToString();
}
}
}

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings
public string TooltipText { get; set; }
public virtual string LabelText
public virtual LocalisableString LabelText
{
get => labelText?.Text ?? string.Empty;
set
@ -69,7 +70,7 @@ namespace osu.Game.Overlays.Settings
set => controlWithCurrent.Current = value;
}
public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText } : new List<string>(Keywords) { LabelText }.ToArray();
public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List<string>(Keywords) { LabelText.ToString() }.ToArray();
public IEnumerable<string> Keywords { get; set; }
@ -120,7 +121,7 @@ namespace osu.Game.Overlays.Settings
labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
}
private class RestoreDefaultValueButton : Container, IHasTooltip
protected internal class RestoreDefaultValueButton : Container, IHasTooltip
{
private Bindable<T> bindable;
@ -146,6 +147,7 @@ namespace osu.Game.Overlays.Settings
RelativeSizeAxes = Axes.Y;
Width = SettingsPanel.CONTENT_MARGINS;
Alpha = 0f;
AlwaysPresent = true;
}
[BackgroundDependencyLoader]
@ -206,7 +208,9 @@ namespace osu.Game.Overlays.Settings
UpdateState();
}
public void UpdateState()
public void UpdateState() => Scheduler.AddOnce(updateState);
private void updateState()
{
if (bindable == null)
return;

View File

@ -37,6 +37,15 @@ namespace osu.Game.Overlays.Toolbar
{
RelativeSizeAxes = Axes.X;
Size = new Vector2(1, HEIGHT);
AlwaysPresent = true;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
// this only needed to be set for the initial LoadComplete/Update, so layout completes and gets buttons in a state they can correctly handle keyboard input for hotkeys.
AlwaysPresent = false;
}
[BackgroundDependencyLoader(true)]

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
@ -12,6 +13,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
@ -43,19 +45,19 @@ namespace osu.Game.Overlays.Toolbar
Texture = textures.Get(texture),
});
public string Text
public LocalisableString Text
{
get => DrawableText.Text;
set => DrawableText.Text = value;
}
public string TooltipMain
public LocalisableString TooltipMain
{
get => tooltip1.Text;
set => tooltip1.Text = value;
}
public string TooltipSub
public LocalisableString TooltipSub
{
get => tooltip2.Text;
set => tooltip2.Text = value;
@ -127,9 +129,9 @@ namespace osu.Game.Overlays.Toolbar
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.Both, // stops us being considered in parent's autosize
Anchor = TooltipAnchor.HasFlag(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight,
Anchor = TooltipAnchor.HasFlagFast(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight,
Origin = TooltipAnchor,
Position = new Vector2(TooltipAnchor.HasFlag(Anchor.x0) ? 5 : -5, 5),
Position = new Vector2(TooltipAnchor.HasFlagFast(Anchor.x0) ? 5 : -5, 5),
Alpha = 0,
Children = new Drawable[]
{

View File

@ -5,14 +5,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game
@ -29,9 +28,6 @@ namespace osu.Game
[Resolved]
private DialogOverlay dialogOverlay { get; set; }
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
@ -81,27 +77,45 @@ namespace osu.Game
game?.CloseAllOverlays(false);
// we may already be at the target screen type.
findValidTarget(current);
}
private bool findValidTarget(IScreen current)
{
var type = current.GetType();
if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled)
// check if we are already at a valid target screen.
if (validScreens.Any(t => t.IsAssignableFrom(type)))
{
finalAction(current);
Cancel();
return;
return true;
}
while (current != null)
{
// if this has a sub stack, recursively check the screens within it.
if (current is IHasSubScreenStack currentSubScreen)
{
if (findValidTarget(currentSubScreen.SubScreenStack.CurrentScreen))
{
// should be correct in theory, but currently untested/unused in existing implementations.
current.MakeCurrent();
return true;
}
}
if (validScreens.Any(t => t.IsAssignableFrom(type)))
{
current.MakeCurrent();
break;
return true;
}
current = current.GetParentScreen();
type = current?.GetType();
}
return false;
}
/// <summary>

View File

@ -3,6 +3,7 @@
using MessagePack;
using Newtonsoft.Json;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Rulesets.Replays;
using osuTK;
@ -31,19 +32,19 @@ namespace osu.Game.Replays.Legacy
[JsonIgnore]
[IgnoreMember]
public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1);
public bool MouseLeft1 => ButtonState.HasFlagFast(ReplayButtonState.Left1);
[JsonIgnore]
[IgnoreMember]
public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1);
public bool MouseRight1 => ButtonState.HasFlagFast(ReplayButtonState.Right1);
[JsonIgnore]
[IgnoreMember]
public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2);
public bool MouseLeft2 => ButtonState.HasFlagFast(ReplayButtonState.Left2);
[JsonIgnore]
[IgnoreMember]
public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2);
public bool MouseRight2 => ButtonState.HasFlagFast(ReplayButtonState.Right2);
[Key(3)]
public ReplayButtonState ButtonState;

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods
public bool RestartOnFail => false;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) };
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) };
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;

View File

@ -0,0 +1,25 @@
// 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.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
{
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
public virtual bool PerformFail() => true;
public virtual bool RestartOnFail => true;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
healthProcessor.FailConditions += FailCondition;
}
protected abstract bool FailCondition(HealthProcessor healthProcessor, JudgementResult result);
}
}

View File

@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "You can't fail, no matter what.";
public override double ScoreMultiplier => 0.5;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) };
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModAutoplay) };
}
}

View File

@ -1,6 +1,8 @@
// 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.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
@ -8,13 +10,18 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModPerfect : ModSuddenDeath
public abstract class ModPerfect : ModFailCondition
{
public override string Name => "Perfect";
public override string Acronym => "PF";
public override IconUsage? Icon => OsuIcon.ModPerfect;
public override ModType Type => ModType.DifficultyIncrease;
public override bool Ranked => true;
public override double ScoreMultiplier => 1;
public override string Description => "SS or quit.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray();
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsAccuracy()
&& result.Type != result.Judgement.MaxResult;

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 osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mods
public double ApplyToRate(double time, double rate) => rate * SpeedChange.Value;
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) };
public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x";
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage? Icon => OsuIcon.ModRelax;
public override ModType Type => ModType.Automation;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) };
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) };
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
@ -9,7 +10,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModSuddenDeath : Mod, IApplicableToHealthProcessor, IApplicableFailOverride
public abstract class ModSuddenDeath : ModFailCondition
{
public override string Name => "Sudden Death";
public override string Acronym => "SD";
@ -18,18 +19,10 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "Miss and fail.";
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
public bool PerformFail() => true;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray();
public bool RestartOnFail => true;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
healthProcessor.FailConditions += FailCondition;
}
protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsCombo()
&& !result.IsHit;
}

View File

@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Adjust pitch", "Should pitch be adjusted with speed")]
public abstract BindableBool AdjustPitch { get; }
public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) };
public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x";
private double finalRateTime;

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Initial rate", "The starting speed of the track")]
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
{
MinValue = 1,
MinValue = 0.51,
MaxValue = 2,
Default = 1,
Value = 1,
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
{
MinValue = 0.5,
MaxValue = 0.99,
MaxValue = 1.99,
Default = 0.75,
Value = 0.75,
Precision = 0.01,
@ -45,5 +45,20 @@ namespace osu.Game.Rulesets.Mods
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray();
public ModWindDown()
{
InitialRate.BindValueChanged(val =>
{
if (val.NewValue <= FinalRate.Value)
FinalRate.Value = val.NewValue - FinalRate.Precision;
});
FinalRate.BindValueChanged(val =>
{
if (val.NewValue >= InitialRate.Value)
InitialRate.Value = val.NewValue + InitialRate.Precision;
});
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods
public override BindableNumber<double> InitialRate { get; } = new BindableDouble
{
MinValue = 0.5,
MaxValue = 1,
MaxValue = 1.99,
Default = 1,
Value = 1,
Precision = 0.01,
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods
[SettingSource("Final rate", "The speed increase to ramp towards")]
public override BindableNumber<double> FinalRate { get; } = new BindableDouble
{
MinValue = 1.01,
MinValue = 0.51,
MaxValue = 2,
Default = 1.5,
Value = 1.5,
@ -45,5 +45,20 @@ namespace osu.Game.Rulesets.Mods
};
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray();
public ModWindUp()
{
InitialRate.BindValueChanged(val =>
{
if (val.NewValue >= FinalRate.Value)
FinalRate.Value = val.NewValue + FinalRate.Precision;
});
FinalRate.BindValueChanged(val =>
{
if (val.NewValue <= InitialRate.Value)
InitialRate.Value = val.NewValue - InitialRate.Precision;
});
}
}
}

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Formats;
using osu.Game.Audio;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Utils;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Skinning;
@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4;
type &= ~LegacyHitObjectType.ComboOffset;
bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);
bool combo = type.HasFlagFast(LegacyHitObjectType.NewCombo);
type &= ~LegacyHitObjectType.NewCombo;
var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
@ -62,14 +63,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
HitObject result = null;
if (type.HasFlag(LegacyHitObjectType.Circle))
if (type.HasFlagFast(LegacyHitObjectType.Circle))
{
result = CreateHit(pos, combo, comboOffset);
if (split.Length > 5)
readCustomSampleBanks(split[5], bankInfo);
}
else if (type.HasFlag(LegacyHitObjectType.Slider))
else if (type.HasFlagFast(LegacyHitObjectType.Slider))
{
double? length = null;
@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
}
else if (type.HasFlag(LegacyHitObjectType.Spinner))
else if (type.HasFlagFast(LegacyHitObjectType.Spinner))
{
double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);
@ -150,7 +151,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (split.Length > 6)
readCustomSampleBanks(split[6], bankInfo);
}
else if (type.HasFlag(LegacyHitObjectType.Hold))
else if (type.HasFlagFast(LegacyHitObjectType.Hold))
{
// Note: Hold is generated by BMS converts
@ -436,16 +437,16 @@ namespace osu.Game.Rulesets.Objects.Legacy
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank,
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal))
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
};
if (type.HasFlag(LegacyHitSoundType.Finish))
if (type.HasFlagFast(LegacyHitSoundType.Finish))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
if (type.HasFlag(LegacyHitSoundType.Whistle))
if (type.HasFlagFast(LegacyHitSoundType.Whistle))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
if (type.HasFlag(LegacyHitSoundType.Clap))
if (type.HasFlagFast(LegacyHitSoundType.Clap))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
return soundTypes;

View File

@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.UI
~DrawableRulesetDependencies()
{
// required to potentially clean up sample store from audio hierarchy.
Dispose(false);
}
@ -134,6 +135,10 @@ namespace osu.Game.Rulesets.UI
public IBindable<double> AggregateTempo => throw new NotSupportedException();
public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public int PlaybackConcurrency
{
get => throw new NotSupportedException();

View File

@ -16,7 +16,6 @@ using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Screens.Play;
using osuTK.Input;
using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.UI
@ -109,9 +108,9 @@ namespace osu.Game.Rulesets.UI
{
switch (e)
{
case MouseDownEvent mouseDown when mouseDown.Button == MouseButton.Left || mouseDown.Button == MouseButton.Right:
case MouseDownEvent _:
if (mouseDisabled.Value)
return false;
return true; // importantly, block upwards propagation so global bindings also don't fire.
break;

View File

@ -137,7 +137,7 @@ namespace osu.Game.Scoring
ScoringMode.BindValueChanged(onScoringModeChanged, true);
}
private IBindable<StarDifficulty> difficultyBindable;
private IBindable<StarDifficulty?> difficultyBindable;
private CancellationTokenSource difficultyCancellationSource;
private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode)
@ -168,7 +168,11 @@ namespace osu.Game.Scoring
// We can compute the max combo locally after the async beatmap difficulty computation.
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
difficultyBindable.BindValueChanged(d =>
{
if (d.NewValue is StarDifficulty diff)
updateScore(diff.MaxCombo);
}, true);
return;
}

View File

@ -495,8 +495,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
// Apply the start time at the newly snapped-to position
double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime;
foreach (HitObject obj in Beatmap.SelectedHitObjects)
obj.StartTime += offset;
Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
}
return true;

View File

@ -320,18 +320,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <param name="sampleName">The name of the hit sample.</param>
public void AddHitSample(string sampleName)
{
EditorBeatmap.BeginChange();
foreach (var h in EditorBeatmap.SelectedHitObjects)
EditorBeatmap.PerformOnSelection(h =>
{
// Make sure there isn't already an existing sample
if (h.Samples.Any(s => s.Name == sampleName))
continue;
return;
h.Samples.Add(new HitSampleInfo(sampleName));
}
EditorBeatmap.EndChange();
});
}
/// <summary>
@ -341,19 +337,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <exception cref="InvalidOperationException">Throws if any selected object doesn't implement <see cref="IHasComboInformation"/></exception>
public void SetNewCombo(bool state)
{
EditorBeatmap.BeginChange();
foreach (var h in EditorBeatmap.SelectedHitObjects)
EditorBeatmap.PerformOnSelection(h =>
{
var comboInfo = h as IHasComboInformation;
if (comboInfo == null || comboInfo.NewCombo == state) continue;
if (comboInfo == null || comboInfo.NewCombo == state) return;
comboInfo.NewCombo = state;
EditorBeatmap.Update(h);
}
EditorBeatmap.EndChange();
});
}
/// <summary>
@ -362,12 +354,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <param name="sampleName">The name of the hit sample.</param>
public void RemoveHitSample(string sampleName)
{
EditorBeatmap.BeginChange();
foreach (var h in EditorBeatmap.SelectedHitObjects)
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
EditorBeatmap.EndChange();
EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName));
}
#endregion

View File

@ -100,6 +100,22 @@ namespace osu.Game.Screens.Edit
private readonly HashSet<HitObject> batchPendingUpdates = new HashSet<HitObject>();
/// <summary>
/// Perform the provided action on every selected hitobject.
/// Changes will be grouped as one history action.
/// </summary>
/// <param name="action">The action to perform.</param>
public void PerformOnSelection(Action<HitObject> action)
{
if (SelectedHitObjects.Count == 0)
return;
BeginChange();
foreach (var h in SelectedHitObjects)
action(h);
EndChange();
}
/// <summary>
/// Adds a collection of <see cref="HitObject"/>s to this <see cref="EditorBeatmap"/>.
/// </summary>

View File

@ -0,0 +1,23 @@
// 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.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Screens
{
/// <summary>
/// Denotes a screen which can handle beatmap / ruleset selection via local logic.
/// This is used in the <see cref="OsuGame.PresentBeatmap"/> flow to handle cases which require custom logic,
/// for instance, if a lease is held on the Beatmap.
/// </summary>
public interface IHandlePresentBeatmap
{
/// <summary>
/// Invoked with a requested beatmap / ruleset for selection.
/// </summary>
/// <param name="beatmap">The beatmap to be selected.</param>
/// <param name="ruleset">The ruleset to be selected.</param>
void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset);
}
}

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 osu.Framework.Screens;
namespace osu.Game.Screens
{
/// <summary>
/// A screen which manages a nested stack of screens within itself.
/// </summary>
public interface IHasSubScreenStack
{
ScreenStack SubScreenStack { get; }
}
}

View File

@ -172,6 +172,18 @@ namespace osu.Game.Screens.Menu
return;
}
// disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805.
if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS)
{
notifications?.Post(new SimpleNotification
{
Text = "Multiplayer is temporarily unavailable on iOS as we figure out some low level issues.",
Icon = FontAwesome.Solid.AppleAlt,
});
return;
}
OnMultiplayer?.Invoke();
}

View File

@ -9,10 +9,15 @@ namespace osu.Game.Screens.Menu
{
public class ConfirmExitDialog : PopupDialog
{
public ConfirmExitDialog(Action confirm, Action cancel)
/// <summary>
/// Construct a new exit confirmation dialog.
/// </summary>
/// <param name="onConfirm">An action to perform on confirmation.</param>
/// <param name="onCancel">An optional action to perform on cancel.</param>
public ConfirmExitDialog(Action onConfirm, Action onCancel = null)
{
HeaderText = "Are you sure you want to exit?";
BodyText = "Last chance to back out.";
HeaderText = "Are you sure you want to exit osu!?";
BodyText = "Last chance to turn back";
Icon = FontAwesome.Solid.ExclamationTriangle;
@ -20,13 +25,13 @@ namespace osu.Game.Screens.Menu
{
new PopupDialogOkButton
{
Text = @"Goodbye",
Action = confirm
Text = @"Let me out!",
Action = onConfirm
},
new PopupDialogCancelButton
{
Text = @"Just a little more",
Action = cancel
Text = @"Just a little more...",
Action = onCancel
},
};
}

View File

@ -9,12 +9,14 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.IO;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit;
using osu.Game.Screens.OnlinePlay.Multiplayer;
@ -23,7 +25,7 @@ using osu.Game.Screens.Select;
namespace osu.Game.Screens.Menu
{
public class MainMenu : OsuScreen
public class MainMenu : OsuScreen, IHandlePresentBeatmap
{
public const float FADE_IN_DURATION = 300;
@ -104,7 +106,7 @@ namespace osu.Game.Screens.Menu
Beatmap.SetDefault();
this.Push(new Editor());
},
OnSolo = onSolo,
OnSolo = loadSoloSongSelect,
OnMultiplayer = () => this.Push(new Multiplayer()),
OnPlaylists = () => this.Push(new Playlists()),
OnExit = confirmAndExit,
@ -160,9 +162,7 @@ namespace osu.Game.Screens.Menu
LoadComponentAsync(songSelect = new PlaySongSelect());
}
public void LoadToSolo() => Schedule(onSolo);
private void onSolo() => this.Push(consumeSongSelect());
private void loadSoloSongSelect() => this.Push(consumeSongSelect());
private Screen consumeSongSelect()
{
@ -289,5 +289,13 @@ namespace osu.Game.Screens.Menu
this.FadeOut(3000);
return base.OnExiting(next);
}
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
{
Beatmap.Value = beatmap;
Ruleset.Value = ruleset;
Schedule(loadSoloSongSelect);
}
}
}

View File

@ -8,8 +8,8 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Graphics;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Menu
{
@ -61,8 +61,8 @@ namespace osu.Game.Screens.Menu
{
var metadata = beatmap.Value.Metadata;
title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title);
artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
this.FadeInFromZero(fade_duration / 2f)
.Delay(4000)

View File

@ -73,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
new OsuSpriteText
{
Text = new LocalisedString((beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist)),
Text = new RomanisableString(beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist),
Font = OsuFont.GetFont(size: TextSize),
},
new OsuSpriteText
@ -83,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
},
new OsuSpriteText
{
Text = new LocalisedString((beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title)),
Text = new RomanisableString(beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title),
Font = OsuFont.GetFont(size: TextSize),
}
}, LinkAction.OpenBeatmap, beatmap.Value.OnlineBeatmapID.ToString(), "Open beatmap");

View File

@ -75,6 +75,14 @@ namespace osu.Game.Screens.OnlinePlay
section.DeselectAll();
}
protected override void OnAvailableModsChanged()
{
base.OnAvailableModsChanged();
foreach (var section in ModSectionsContainer.Children)
((FreeModSection)section).UpdateCheckboxState();
}
protected override ModSection CreateModSection(ModType type) => new FreeModSection(type);
private class FreeModSection : ModSection
@ -108,10 +116,14 @@ namespace osu.Game.Screens.OnlinePlay
protected override void ModButtonStateChanged(Mod mod)
{
base.ModButtonStateChanged(mod);
UpdateCheckboxState();
}
public void UpdateCheckboxState()
{
if (!SelectionAnimationRunning)
{
var validButtons = ButtonsContainer.OfType<ModButton>().Where(b => b.Mod.HasImplementation);
var validButtons = Buttons.Where(b => b.Mod.HasImplementation);
checkbox.Current.Value = validButtons.All(b => b.Selected);
}
}

View File

@ -4,9 +4,11 @@
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select;
@ -19,6 +21,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private LoadingLayer loadingLayer;
/// <summary>
/// Construct a new instance of multiplayer song select.
/// </summary>
/// <param name="beatmap">An optional initial beatmap selection to perform.</param>
/// <param name="ruleset">An optional initial ruleset selection to perform.</param>
public MultiplayerMatchSongSelect(WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
{
if (beatmap != null || ruleset != null)
{
Schedule(() =>
{
if (beatmap != null) Beatmap.Value = beatmap;
if (ruleset != null) Ruleset.Value = ruleset;
});
}
}
[BackgroundDependencyLoader]
private void load()
{

View File

@ -12,9 +12,13 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match;
@ -29,7 +33,7 @@ using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.Pa
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
[Cached]
public class MultiplayerMatchSubScreen : RoomSubScreen
public class MultiplayerMatchSubScreen : RoomSubScreen, IHandlePresentBeatmap
{
public override string Title { get; }
@ -279,14 +283,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList();
}
[Resolved(canBeNull: true)]
private DialogOverlay dialogOverlay { get; set; }
private bool exitConfirmed;
public override bool OnBackButton()
{
if (client.Room != null && settingsOverlay.State.Value == Visibility.Visible)
if (client.Room == null)
{
// room has not been created yet; exit immediately.
return base.OnBackButton();
}
if (settingsOverlay.State.Value == Visibility.Visible)
{
settingsOverlay.Hide();
return true;
}
if (!exitConfirmed && dialogOverlay != null)
{
dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () =>
{
exitConfirmed = true;
this.Exit();
}));
return true;
}
return base.OnBackButton();
}
@ -394,5 +420,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
modSettingChangeTracker?.Dispose();
}
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
{
if (!this.IsCurrentScreen())
return;
if (!client.IsHost)
{
// todo: should handle this when the request queue is implemented.
// if we decide that the presentation should exit the user from the multiplayer game, the PresentBeatmap
// flow may need to change to support an "unable to present" return value.
return;
}
this.Push(new MultiplayerMatchSongSelect(beatmap, ruleset));
}
}
}

View File

@ -28,7 +28,7 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay
{
[Cached]
public abstract class OnlinePlayScreen : OsuScreen
public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack
{
public override bool CursorVisible => (screenStack.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
@ -355,5 +355,7 @@ namespace osu.Game.Screens.OnlinePlay
protected override double TransformDuration => 200;
}
}
ScreenStack IHasSubScreenStack.SubScreenStack => screenStack;
}
}

View File

@ -75,9 +75,18 @@ namespace osu.Game.Screens.OnlinePlay
Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty<Mod>();
Mods.BindValueChanged(onModsChanged);
Ruleset.BindValueChanged(onRulesetChanged);
}
private void onModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
{
FreeMods.Value = FreeMods.Value.Where(checkCompatibleFreeMod).ToList();
// Reset the validity delegate to update the overlay's display.
freeModSelectOverlay.IsValidMod = IsValidFreeMod;
}
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> ruleset)
{
FreeMods.Value = Array.Empty<Mod>();
@ -155,6 +164,10 @@ namespace osu.Game.Screens.OnlinePlay
/// </summary>
/// <param name="mod">The <see cref="Mod"/> to check.</param>
/// <returns>Whether <paramref name="mod"/> is a selectable free-mod.</returns>
protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod);
protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod) && checkCompatibleFreeMod(mod);
private bool checkCompatibleFreeMod(Mod mod)
=> Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods.
&& ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods.
}
}

View File

@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -362,7 +363,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Menu.MaxHeight = 100;
}
protected override string GenerateItemText(TimeSpan item) => item.Humanize();
protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize();
}
}
}

View File

@ -73,7 +73,7 @@ namespace osu.Game.Screens.Play
}),
new OsuSpriteText
{
Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)),
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
Font = OsuFont.GetFont(size: 36, italics: true),
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
@ -81,7 +81,7 @@ namespace osu.Game.Screens.Play
},
new OsuSpriteText
{
Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)),
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
Font = OsuFont.GetFont(size: 26, italics: true),
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,

View File

@ -427,11 +427,18 @@ namespace osu.Game.Screens.Play
private void updatePauseOnFocusLostState()
{
if (!PauseOnFocusLost || breakTracker.IsBreakTime.Value)
if (!PauseOnFocusLost || !pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value)
return;
if (gameActive.Value == false)
Pause();
{
bool paused = Pause();
// if the initial pause could not be satisfied, the pause cooldown may be active.
// reschedule the pause attempt until it can be achieved.
if (!paused)
Scheduler.AddOnce(updatePauseOnFocusLostState);
}
}
private IBeatmap loadPlayableBeatmap()
@ -674,6 +681,9 @@ namespace osu.Game.Screens.Play
private double? lastPauseActionTime;
protected bool PauseCooldownActive =>
lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown;
/// <summary>
/// A set of conditionals which defines whether the current game state and configuration allows for
/// pausing to be attempted via <see cref="Pause"/>. If false, the game should generally exit if a user pause
@ -684,11 +694,9 @@ namespace osu.Game.Screens.Play
LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume
// replays cannot be paused and exit immediately
&& !DrawableRuleset.HasReplayLoaded.Value
// cannot pause if we are already in a fail state
&& !HasFailed;
private bool pauseCooldownActive =>
lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown;
private bool canResume =>
// cannot resume from a non-paused state
GameplayClockContainer.IsPaused.Value
@ -697,12 +705,12 @@ namespace osu.Game.Screens.Play
// already resuming
&& !IsResuming;
public void Pause()
public bool Pause()
{
if (!pausingSupportedByCurrentState) return;
if (!pausingSupportedByCurrentState) return false;
if (!IsResuming && pauseCooldownActive)
return;
if (!IsResuming && PauseCooldownActive)
return false;
if (IsResuming)
{
@ -713,6 +721,7 @@ namespace osu.Game.Screens.Play
GameplayClockContainer.Stop();
PauseOverlay.Show();
lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime;
return true;
}
public void Resume()

View File

@ -3,7 +3,6 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;

View File

@ -101,7 +101,7 @@ namespace osu.Game.Screens.Ranking.Expanded
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)),
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
Truncate = true,
@ -110,7 +110,7 @@ namespace osu.Game.Screens.Ranking.Expanded
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)),
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
Truncate = true,

View File

@ -15,6 +15,8 @@ namespace osu.Game.Screens.Ranking
{
public class SoloResultsScreen : ResultsScreen
{
private GetScoresRequest getScoreRequest;
[Resolved]
private RulesetStore rulesets { get; set; }
@ -28,9 +30,16 @@ namespace osu.Game.Screens.Ranking
if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending)
return null;
var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset);
req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets)));
return req;
getScoreRequest = new GetScoresRequest(Score.Beatmap, Score.Ruleset);
getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets)));
return getScoreRequest;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
getScoreRequest?.Cancel();
}
}
}

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using JetBrains.Annotations;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
@ -25,6 +24,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@ -38,12 +38,16 @@ namespace osu.Game.Screens.Select
private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0);
private readonly IBindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
private IBindable<StarDifficulty> beatmapDifficulty;
private IBindable<StarDifficulty?> beatmapDifficulty;
protected BufferedWedgeInfo Info;
@ -63,11 +67,10 @@ namespace osu.Game.Screens.Select
};
}
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] Bindable<RulesetInfo> parentRuleset)
[BackgroundDependencyLoader]
private void load()
{
ruleset.BindTo(parentRuleset);
ruleset.ValueChanged += _ => updateDisplay();
ruleset.BindValueChanged(_ => updateDisplay());
}
protected override void PopIn()
@ -132,7 +135,7 @@ namespace osu.Game.Screens.Select
return;
}
LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value)
LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value ?? new StarDifficulty())
{
Shear = -Shear,
Depth = Info?.Depth + 1 ?? 0
@ -160,20 +163,25 @@ namespace osu.Game.Screens.Select
public OsuSpriteText ArtistLabel { get; private set; }
public BeatmapSetOnlineStatusPill StatusPill { get; private set; }
public FillFlowContainer MapperContainer { get; private set; }
public FillFlowContainer InfoLabelContainer { get; private set; }
private ILocalisedBindableString titleBinding;
private ILocalisedBindableString artistBinding;
private FillFlowContainer infoLabelContainer;
private Container bpmLabelContainer;
private readonly WorkingBeatmap beatmap;
private readonly RulesetInfo ruleset;
private readonly IReadOnlyList<Mod> mods;
private readonly StarDifficulty starDifficulty;
public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, StarDifficulty difficulty)
private ModSettingChangeTracker settingChangeTracker;
public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList<Mod> mods, StarDifficulty difficulty)
: base(pixelSnapping: true)
{
this.beatmap = beatmap;
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
this.mods = mods;
starDifficulty = difficulty;
}
@ -184,11 +192,10 @@ namespace osu.Game.Screens.Select
var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
CacheDrawnFrameBuffer = true;
RelativeSizeAxes = Axes.Both;
titleBinding = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title)));
artistBinding = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist)));
titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title));
artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist));
Children = new Drawable[]
{
@ -302,12 +309,11 @@ namespace osu.Game.Screens.Select
AutoSizeAxes = Axes.Both,
Children = getMapper(metadata)
},
InfoLabelContainer = new FillFlowContainer
infoLabelContainer = new FillFlowContainer
{
Margin = new MarginPadding { Top = 20 },
Spacing = new Vector2(20, 0),
AutoSizeAxes = Axes.Both,
Children = getInfoLabels()
}
}
}
@ -319,6 +325,8 @@ namespace osu.Game.Screens.Select
// no difficulty means it can't have a status to show
if (beatmapInfo.Version == null)
StatusPill.Hide();
addInfoLabels();
}
private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0
@ -335,63 +343,91 @@ namespace osu.Game.Screens.Select
ForceRedraw();
}
private InfoLabel[] getInfoLabels()
private void addInfoLabels()
{
var b = beatmap.Beatmap;
if (beatmap.Beatmap?.HitObjects?.Any() != true)
return;
List<InfoLabel> labels = new List<InfoLabel>();
if (b?.HitObjects?.Any() == true)
infoLabelContainer.Children = new Drawable[]
{
labels.Add(new InfoLabel(new BeatmapStatistic
new InfoLabel(new BeatmapStatistic
{
Name = "Length",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"),
}));
labels.Add(new InfoLabel(new BeatmapStatistic
Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"),
}),
bpmLabelContainer = new Container
{
Name = "BPM",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
Content = getBPMRange(b),
}));
AutoSizeAxes = Axes.Both,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(20, 0),
Children = getRulesetInfoLabels()
}
};
settingChangeTracker = new ModSettingChangeTracker(mods);
settingChangeTracker.SettingChanged += _ => refreshBPMLabel();
refreshBPMLabel();
}
private InfoLabel[] getRulesetInfoLabels()
{
try
{
IBeatmap playableBeatmap;
try
{
IBeatmap playableBeatmap;
try
{
// Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
}
catch (BeatmapInvalidForRulesetException)
{
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty<Mod>());
}
labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
// Try to get the beatmap with the user's ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty<Mod>());
}
catch (Exception e)
catch (BeatmapInvalidForRulesetException)
{
Logger.Error(e, "Could not load beatmap successfully!");
// Can't be converted to the user's ruleset, so use the beatmap's own ruleset
playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty<Mod>());
}
return playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)).ToArray();
}
catch (Exception e)
{
Logger.Error(e, "Could not load beatmap successfully!");
}
return labels.ToArray();
return Array.Empty<InfoLabel>();
}
private string getBPMRange(IBeatmap beatmap)
private void refreshBPMLabel()
{
double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
var b = beatmap.Beatmap;
if (b == null)
return;
if (Precision.AlmostEquals(bpmMin, bpmMax))
return $"{bpmMin:0}";
// this doesn't consider mods which apply variable rates, yet.
double rate = 1;
foreach (var mod in mods.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate);
return $"{bpmMin:0}-{bpmMax:0} (mostly {60000 / beatmap.GetMostCommonBeatLength():0})";
double bpmMax = b.ControlPointInfo.BPMMaximum * rate;
double bpmMin = b.ControlPointInfo.BPMMinimum * rate;
double mostCommonBPM = 60000 / b.GetMostCommonBeatLength() * rate;
string labelText = Precision.AlmostEquals(bpmMin, bpmMax)
? $"{bpmMin:0}"
: $"{bpmMin:0}-{bpmMax:0} (mostly {mostCommonBPM:0})";
bpmLabelContainer.Child = new InfoLabel(new BeatmapStatistic
{
Name = "BPM",
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
Content = labelText
});
ForceRedraw();
}
private OsuSpriteText[] getMapper(BeatmapMetadata metadata)
@ -414,6 +450,12 @@ namespace osu.Game.Screens.Select
};
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
settingChangeTracker?.Dispose();
}
public class InfoLabel : Container, IHasTooltip
{
public string TooltipText { get; }

View File

@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved(CanBeNull = true)]
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
private IBindable<StarDifficulty> starDifficultyBindable;
private IBindable<StarDifficulty?> starDifficultyBindable;
private CancellationTokenSource starDifficultyCancellationSource;
public DrawableCarouselBeatmap(CarouselBeatmap panel)
@ -217,7 +217,10 @@ namespace osu.Game.Screens.Select.Carousel
{
// We've potentially cancelled the computation above so a new bindable is required.
starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token);
starDifficultyBindable.BindValueChanged(d => starCounter.Current = (float)d.NewValue.Stars, true);
starDifficultyBindable.BindValueChanged(d =>
{
starCounter.Current = (float)(d.NewValue?.Stars ?? 0);
}, true);
}
base.ApplyState();

View File

@ -41,13 +41,13 @@ namespace osu.Game.Screens.Select.Carousel
{
new OsuSpriteText
{
Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)),
Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title),
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
Shadow = true,
},
new OsuSpriteText
{
Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)),
Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
Shadow = true,
},

View File

@ -15,6 +15,8 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Mods;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Configuration;
@ -137,8 +139,6 @@ namespace osu.Game.Screens.Select.Details
updateStarDifficulty();
}
private IBindable<StarDifficulty> normalStarDifficulty;
private IBindable<StarDifficulty> moddedStarDifficulty;
private CancellationTokenSource starDifficultyCancellationSource;
private void updateStarDifficulty()
@ -150,13 +150,13 @@ namespace osu.Game.Screens.Select.Details
starDifficultyCancellationSource = new CancellationTokenSource();
normalStarDifficulty = difficultyCache.GetBindableDifficulty(Beatmap, ruleset.Value, null, starDifficultyCancellationSource.Token);
moddedStarDifficulty = difficultyCache.GetBindableDifficulty(Beatmap, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token);
var normalStarDifficulty = difficultyCache.GetDifficultyAsync(Beatmap, ruleset.Value, null, starDifficultyCancellationSource.Token);
var moddedStarDifficulty = difficultyCache.GetDifficultyAsync(Beatmap, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token);
normalStarDifficulty.BindValueChanged(_ => updateDisplay());
moddedStarDifficulty.BindValueChanged(_ => updateDisplay(), true);
void updateDisplay() => starDifficulty.Value = ((float)normalStarDifficulty.Value.Stars, (float)moddedStarDifficulty.Value.Stars);
Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() =>
{
starDifficulty.Value = ((float)normalStarDifficulty.Result.Stars, (float)moddedStarDifficulty.Result.Stars);
}), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
}
protected override void Dispose(bool isDisposing)
@ -180,7 +180,7 @@ namespace osu.Game.Screens.Select.Details
[Resolved]
private OsuColour colours { get; set; }
public string Title
public LocalisableString Title
{
get => name.Text;
set => name.Text = value;

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.Containers;
@ -21,9 +22,9 @@ namespace osu.Game.Screens.Select
protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0);
public string Text
public LocalisableString Text
{
get => SpriteText?.Text;
get => SpriteText?.Text ?? default;
set
{
if (SpriteText != null)

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
@ -39,13 +40,13 @@ namespace osu.Game.Screens.Select.Options
set => iconText.Icon = value;
}
public string FirstLineText
public LocalisableString FirstLineText
{
get => firstLine.Text;
set => firstLine.Text = value;
}
public string SecondLineText
public LocalisableString SecondLineText
{
get => secondLine.Text;
set => secondLine.Text = value;

View File

@ -17,7 +17,7 @@ namespace osu.Game.Skinning
/// <summary>
/// A sample corresponding to an <see cref="ISampleInfo"/> that supports being pooled and responding to skin changes.
/// </summary>
public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent
public class PoolableSkinnableSample : SkinReloadableDrawable, IAdjustableAudioComponent
{
/// <summary>
/// The currently-loaded <see cref="DrawableSample"/>.
@ -165,6 +165,10 @@ namespace osu.Game.Skinning
public BindableNumber<double> Tempo => sampleContainer.Tempo;
public void BindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.BindAdjustments(component);
public void UnbindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.UnbindAdjustments(component);
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable);
public void RemoveAdjustment(AdjustableProperty type, IBindable<double> adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);

View File

@ -36,6 +36,7 @@ namespace osu.Game.Skinning
~Skin()
{
// required to potentially clean up sample store from audio hierarchy.
Dispose(false);
}

View File

@ -176,6 +176,16 @@ namespace osu.Game.Skinning
public BindableNumber<double> Tempo => samplesContainer.Tempo;
public void BindAdjustments(IAggregateAudioAdjustment component)
{
samplesContainer.BindAdjustments(component);
}
public void UnbindAdjustments(IAggregateAudioAdjustment component)
{
samplesContainer.UnbindAdjustments(component);
}
public void AddAdjustment(AdjustableProperty type, IBindable<double> adjustBindable)
=> samplesContainer.AddAdjustment(type, adjustBindable);
@ -192,6 +202,14 @@ namespace osu.Game.Skinning
public bool IsPlayed => samplesContainer.Any(s => s.Played);
public IBindable<double> AggregateVolume => samplesContainer.AggregateVolume;
public IBindable<double> AggregateBalance => samplesContainer.AggregateBalance;
public IBindable<double> AggregateFrequency => samplesContainer.AggregateFrequency;
public IBindable<double> AggregateTempo => samplesContainer.AggregateTempo;
#endregion
}
}

View File

@ -3,6 +3,7 @@
using System;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
namespace osu.Game.Skinning
{
@ -21,9 +22,9 @@ namespace osu.Game.Skinning
textDrawable.Text = Text;
}
private string text;
private LocalisableString text;
public string Text
public LocalisableString Text
{
get => text;
set

View File

@ -3,6 +3,7 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Textures;
@ -80,17 +81,17 @@ namespace osu.Game.Storyboards.Drawables
if (FlipH)
{
if (origin.HasFlag(Anchor.x0))
if (origin.HasFlagFast(Anchor.x0))
origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
else if (origin.HasFlag(Anchor.x2))
else if (origin.HasFlagFast(Anchor.x2))
origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
}
if (FlipV)
{
if (origin.HasFlag(Anchor.y0))
if (origin.HasFlagFast(Anchor.y0))
origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
else if (origin.HasFlag(Anchor.y2))
else if (origin.HasFlagFast(Anchor.y2))
origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
}

View File

@ -3,6 +3,7 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
@ -80,17 +81,17 @@ namespace osu.Game.Storyboards.Drawables
if (FlipH)
{
if (origin.HasFlag(Anchor.x0))
if (origin.HasFlagFast(Anchor.x0))
origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
else if (origin.HasFlag(Anchor.x2))
else if (origin.HasFlagFast(Anchor.x2))
origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2));
}
if (FlipV)
{
if (origin.HasFlag(Anchor.y0))
if (origin.HasFlagFast(Anchor.y0))
origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
else if (origin.HasFlag(Anchor.y2))
else if (origin.HasFlagFast(Anchor.y2))
origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2));
}

View File

@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual
public new HealthProcessor HealthProcessor => base.HealthProcessor;
public new bool PauseCooldownActive => base.PauseCooldownActive;
public readonly List<JudgementResult> Results = new List<JudgementResult>();
public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)

View File

@ -86,11 +86,6 @@ namespace osu.Game.Utils
#region Disposal
~SentryLogger()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);

Some files were not shown because too many files have changed in this diff Show More