Merge branch 'master' into no-gameplay-clock-gameplay-offset

This commit is contained in:
Dean Herbert
2022-08-25 16:24:55 +09:00
32 changed files with 934 additions and 461 deletions

View File

@ -69,8 +69,9 @@ namespace osu.Game.Database
/// 21 2022-07-27 Migrate collections to realm (BeatmapCollection).
/// 22 2022-07-31 Added ModPreset.
/// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo.
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
/// </summary>
private const int schema_version = 23;
private const int schema_version = 24;
/// <summary>
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.

View File

@ -74,6 +74,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty("statistics")]
public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
[JsonProperty("maximum_statistics")]
public Dictionary<HitResult, int> MaximumStatistics { get; set; } = new Dictionary<HitResult, int>();
#region osu-web API additions (not stored to database).
[JsonProperty("id")]
@ -153,6 +156,7 @@ namespace osu.Game.Online.API.Requests.Responses
MaxCombo = MaxCombo,
Rank = Rank,
Statistics = Statistics,
MaximumStatistics = MaximumStatistics,
Date = EndedAt,
Hash = HasReplay ? "online" : string.Empty, // TODO: temporary?
Mods = mods,
@ -174,6 +178,7 @@ namespace osu.Game.Online.API.Requests.Responses
Passed = score.Passed,
Mods = score.APIMods,
Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
};
public long OnlineID => ID ?? -1;

View File

@ -1,27 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Online.Chat
{
public class ExternalLinkOpener : Component
{
[Resolved]
private GameHost host { get; set; }
private GameHost host { get; set; } = null!;
[Resolved(CanBeNull = true)]
private IDialogOverlay dialogOverlay { get; set; }
private IDialogOverlay? dialogOverlay { get; set; }
private Bindable<bool> externalLinkWarning;
private Bindable<bool> externalLinkWarning = null!;
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config)
@ -31,10 +31,39 @@ namespace osu.Game.Online.Chat
public void OpenUrlExternally(string url, bool bypassWarning = false)
{
if (!bypassWarning && externalLinkWarning.Value)
dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url)));
if (!bypassWarning && externalLinkWarning.Value && dialogOverlay != null)
dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url), () => host.GetClipboard()?.SetText(url)));
else
host.OpenUrlExternally(url);
}
public class ExternalLinkDialog : PopupDialog
{
public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction)
{
HeaderText = "Just checking...";
BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}";
Icon = FontAwesome.Solid.ExclamationTriangle;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"Yes. Go for it.",
Action = openExternalLinkAction
},
new PopupDialogCancelButton
{
Text = @"Copy URL to the clipboard instead.",
Action = copyExternalLinkAction
},
new PopupDialogCancelButton
{
Text = @"No! Abort mission!"
},
};
}
}
}
}

View File

@ -1,35 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Chat
{
public class ExternalLinkDialog : PopupDialog
{
public ExternalLinkDialog(string url, Action openExternalLinkAction)
{
HeaderText = "Just checking...";
BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}";
Icon = FontAwesome.Solid.ExclamationTriangle;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = @"Yes. Go for it.",
Action = openExternalLinkAction
},
new PopupDialogCancelButton
{
Text = @"No! Abort mission!"
},
};
}
}
}

View File

@ -128,8 +128,7 @@ namespace osu.Game.Rulesets.Scoring
private bool beatmapApplied;
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
private Dictionary<HitResult, int>? maximumResultCounts;
private readonly Dictionary<HitResult, int> maximumResultCounts = new Dictionary<HitResult, int>();
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
private HitObject? lastHitObject;
@ -405,8 +404,6 @@ namespace osu.Game.Rulesets.Scoring
return ScoreRank.D;
}
public int GetStatistic(HitResult result) => scoreResultCounts.GetValueOrDefault(result);
/// <summary>
/// Resets this ScoreProcessor to a default state.
/// </summary>
@ -421,7 +418,9 @@ namespace osu.Game.Rulesets.Scoring
if (storeResults)
{
maximumScoringValues = currentScoringValues;
maximumResultCounts = new Dictionary<HitResult, int>(scoreResultCounts);
maximumResultCounts.Clear();
maximumResultCounts.AddRange(scoreResultCounts);
}
scoreResultCounts.Clear();
@ -449,7 +448,10 @@ namespace osu.Game.Rulesets.Scoring
score.HitEvents = hitEvents;
foreach (var result in HitResultExtensions.ALL_TYPES)
score.Statistics[result] = GetStatistic(result);
score.Statistics[result] = scoreResultCounts.GetValueOrDefault(result);
foreach (var result in HitResultExtensions.ALL_TYPES)
score.MaximumStatistics[result] = maximumResultCounts.GetValueOrDefault(result);
// Populate total score after everything else.
score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score));

View File

@ -73,6 +73,9 @@ namespace osu.Game.Scoring
if (string.IsNullOrEmpty(model.StatisticsJson))
model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics);
if (string.IsNullOrEmpty(model.MaximumStatisticsJson))
model.MaximumStatisticsJson = JsonConvert.SerializeObject(model.MaximumStatistics);
}
protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport)

View File

@ -63,6 +63,9 @@ namespace osu.Game.Scoring
[MapTo("Statistics")]
public string StatisticsJson { get; set; } = string.Empty;
[MapTo("MaximumStatistics")]
public string MaximumStatisticsJson { get; set; } = string.Empty;
public ScoreInfo(BeatmapInfo? beatmap = null, RulesetInfo? ruleset = null, RealmUser? realmUser = null)
{
Ruleset = ruleset ?? new RulesetInfo();
@ -133,6 +136,7 @@ namespace osu.Game.Scoring
var clone = (ScoreInfo)this.Detach().MemberwiseClone();
clone.Statistics = new Dictionary<HitResult, int>(clone.Statistics);
clone.MaximumStatistics = new Dictionary<HitResult, int>(clone.MaximumStatistics);
clone.RealmUser = new RealmUser
{
OnlineID = RealmUser.OnlineID,
@ -181,6 +185,24 @@ namespace osu.Game.Scoring
set => statistics = value;
}
private Dictionary<HitResult, int>? maximumStatistics;
[Ignored]
public Dictionary<HitResult, int> MaximumStatistics
{
get
{
if (maximumStatistics != null)
return maximumStatistics;
if (!string.IsNullOrEmpty(MaximumStatisticsJson))
maximumStatistics = JsonConvert.DeserializeObject<Dictionary<HitResult, int>>(MaximumStatisticsJson);
return maximumStatistics ??= new Dictionary<HitResult, int>();
}
set => maximumStatistics = value;
}
private Mod[]? mods;
[Ignored]

View File

@ -55,8 +55,6 @@ namespace osu.Game.Screens.Select.Carousel
match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) ||
criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode);
match &= criteria.Sort != SortMode.DateRanked || BeatmapInfo.BeatmapSet?.DateRanked != null;
match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating);
if (match && criteria.SearchTerms.Length > 0)

View File

@ -99,6 +99,13 @@ namespace osu.Game.Screens.Select.Carousel
case SortMode.Difficulty:
return compareUsingAggregateMax(otherSet, b => b.StarRating);
case SortMode.DateSubmitted:
// Beatmaps which have no submitted date should already be filtered away in this mode.
if (BeatmapSet.DateSubmitted == null || otherSet.BeatmapSet.DateSubmitted == null)
return 0;
return otherSet.BeatmapSet.DateSubmitted.Value.CompareTo(BeatmapSet.DateSubmitted.Value);
}
}
@ -122,7 +129,12 @@ namespace osu.Game.Screens.Select.Carousel
public override void Filter(FilterCriteria criteria)
{
base.Filter(criteria);
Filtered.Value = Items.All(i => i.Filtered.Value);
bool match = Items.All(i => i.Filtered.Value);
match &= criteria.Sort != SortMode.DateRanked || BeatmapSet?.DateRanked != null;
match &= criteria.Sort != SortMode.DateSubmitted || BeatmapSet?.DateSubmitted != null;
Filtered.Value = match;
}
public override string ToString() => BeatmapSet.ToString();

View File

@ -20,6 +20,9 @@ namespace osu.Game.Screens.Select.Filter
[LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksBpm))]
BPM,
[Description("Date Submitted")]
DateSubmitted,
[Description("Date Added")]
DateAdded,