1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 21:07:33 +08:00
osu-lazer/osu.Game/Online/API/APIMod.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

114 lines
3.8 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using MessagePack;
using Newtonsoft.Json;
using osu.Framework.Bindables;
Change `ToMod` to return an `UnknownMod` rather than throw if a mod isn't available As seen by this kind of crash, having the `.ToMod` method throw can be very problematic and also hidden (as it is used inside of models in places where exceptions are not expected to occur). Given there are tens of usages of this method, returning a placeholder mod seems like a better idea than outright throwing. ``` An unhandled has occurred. System.InvalidOperationException: There is no mod in the ruleset (osu) matching the acronym AS. at osu.Game.Online.API.APIMod.ToMod(Ruleset ruleset) in /Users/dean/Projects/osu/osu.Game/Online/API/APIMod.cs:line 54 at osu.Game.Scoring.ScoreInfo.<get_Mods>b__117_0(APIMod m) in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at System.Linq.Enumerable.SelectArrayIterator`2.ToArray() at osu.Game.Scoring.ScoreInfo.get_Mods() in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c.<subscribeToLocalScores>b__40_2(ScoreInfo s) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 199 at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext() at osu.Game.Database.RealmObjectExtensions.Detach[T](IEnumerable`1 items) in /Users/dean/Projects/osu/osu.Game/Database/RealmObjectExtensions.cs:line 180 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c__DisplayClass40_0.<subscribeToLocalScores>g__localScoresChanged|1(IRealmCollection`1 sender, ChangeSet changes, Exception exception) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 209 at Realms.RealmCollectionBase`1.Realms.INotifiable<Realms.NotifiableObjectHandleBase.CollectionChangeSet>.NotifyCallbacks(Nullable`1 changes, Nullable`1 exception) at Realms.NotifiableObjectHandleBase.NotifyObjectChanged(IntPtr managedHandle, IntPtr changes, IntPtr exception) ```
2022-03-09 16:13:51 +08:00
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Online.API
{
[MessagePackObject]
public class APIMod : IEquatable<APIMod>
{
[JsonProperty("acronym")]
[Key(0)]
2022-08-04 18:15:28 +08:00
public string Acronym { get; set; } = string.Empty;
[JsonProperty("settings")]
[Key(1)]
[MessagePackFormatter(typeof(ModSettingsDictionaryFormatter))]
public Dictionary<string, object> Settings { get; set; } = new Dictionary<string, object>();
[JsonConstructor]
[SerializationConstructor]
public APIMod()
{
}
public APIMod(Mod mod)
{
Acronym = mod.Acronym;
foreach (var (_, property) in mod.GetSettingsSourceProperties())
{
var bindable = (IBindable)property.GetValue(mod)!;
if (!bindable.IsDefault)
Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue());
}
}
2022-08-04 18:15:28 +08:00
public Mod ToMod(Ruleset ruleset)
{
Mod? resultMod = ruleset.CreateModFromAcronym(Acronym);
if (resultMod == null)
Change `ToMod` to return an `UnknownMod` rather than throw if a mod isn't available As seen by this kind of crash, having the `.ToMod` method throw can be very problematic and also hidden (as it is used inside of models in places where exceptions are not expected to occur). Given there are tens of usages of this method, returning a placeholder mod seems like a better idea than outright throwing. ``` An unhandled has occurred. System.InvalidOperationException: There is no mod in the ruleset (osu) matching the acronym AS. at osu.Game.Online.API.APIMod.ToMod(Ruleset ruleset) in /Users/dean/Projects/osu/osu.Game/Online/API/APIMod.cs:line 54 at osu.Game.Scoring.ScoreInfo.<get_Mods>b__117_0(APIMod m) in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at System.Linq.Enumerable.SelectArrayIterator`2.ToArray() at osu.Game.Scoring.ScoreInfo.get_Mods() in /Users/dean/Projects/osu/osu.Game/Scoring/ScoreInfo.cs:line 193 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c.<subscribeToLocalScores>b__40_2(ScoreInfo s) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 199 at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext() at osu.Game.Database.RealmObjectExtensions.Detach[T](IEnumerable`1 items) in /Users/dean/Projects/osu/osu.Game/Database/RealmObjectExtensions.cs:line 180 at osu.Game.Screens.Select.Leaderboards.BeatmapLeaderboard.<>c__DisplayClass40_0.<subscribeToLocalScores>g__localScoresChanged|1(IRealmCollection`1 sender, ChangeSet changes, Exception exception) in /Users/dean/Projects/osu/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs:line 209 at Realms.RealmCollectionBase`1.Realms.INotifiable<Realms.NotifiableObjectHandleBase.CollectionChangeSet>.NotifyCallbacks(Nullable`1 changes, Nullable`1 exception) at Realms.NotifiableObjectHandleBase.NotifyObjectChanged(IntPtr managedHandle, IntPtr changes, IntPtr exception) ```
2022-03-09 16:13:51 +08:00
{
Logger.Log($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}.");
return new UnknownMod(Acronym);
}
if (Settings.Count > 0)
{
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
{
if (!Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue))
continue;
try
{
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod)!, settingValue);
}
catch (Exception ex)
{
Logger.Log($"Failed to copy mod setting value '{settingValue}' to \"{property.Name}\": {ex.Message}");
}
}
}
return resultMod;
}
2022-08-04 18:15:28 +08:00
public bool ShouldSerializeSettings() => Settings.Count > 0;
public bool Equals(APIMod? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
}
2020-10-22 16:38:16 +08:00
public override string ToString()
{
if (Settings.Count > 0)
return $"{Acronym} ({string.Join(',', Settings.Select(kvp => $"{kvp.Key}:{kvp.Value}"))})";
return $"{Acronym}";
}
private class ModSettingsEqualityComparer : IEqualityComparer<KeyValuePair<string, object>>
{
public static ModSettingsEqualityComparer Default { get; } = new ModSettingsEqualityComparer();
public bool Equals(KeyValuePair<string, object> x, KeyValuePair<string, object> y)
{
object xValue = x.Value.GetUnderlyingSettingValue();
object yValue = y.Value.GetUnderlyingSettingValue();
return x.Key == y.Key && EqualityComparer<object>.Default.Equals(xValue, yValue);
}
public int GetHashCode(KeyValuePair<string, object> obj) => HashCode.Combine(obj.Key, obj.Value.GetUnderlyingSettingValue());
}
}
}