osu/osu.Game/Scoring/ScoreInfo.cs

269 lines
7.7 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.
2018-04-13 09:19:50 +00:00
using System;
using System.Collections.Generic;
2018-11-28 07:39:08 +00:00
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
2018-09-28 09:29:49 +00:00
using Newtonsoft.Json;
2021-07-23 20:37:08 +00:00
using osu.Framework.Localisation;
2018-04-13 09:19:50 +00:00
using osu.Game.Beatmaps;
2018-11-28 07:39:08 +00:00
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
2018-11-28 07:33:42 +00:00
using osu.Game.Rulesets;
2018-04-13 09:19:50 +00:00
using osu.Game.Rulesets.Mods;
2018-11-28 07:33:42 +00:00
using osu.Game.Rulesets.Scoring;
using osu.Game.Users;
using osu.Game.Utils;
2018-04-13 09:19:50 +00:00
2018-11-28 07:12:57 +00:00
namespace osu.Game.Scoring
2018-04-13 09:19:50 +00:00
{
2021-10-28 08:57:17 +00:00
public class ScoreInfo : IScoreInfo, IHasFiles<ScoreFileInfo>, IHasPrimaryKey, ISoftDelete, IEquatable<ScoreInfo>, IDeepCloneable<ScoreInfo>
2018-04-13 09:19:50 +00:00
{
2018-11-28 07:39:08 +00:00
public int ID { get; set; }
public bool IsManaged => ID > 0;
2018-04-13 09:19:50 +00:00
public ScoreRank Rank { get; set; }
2019-02-26 04:10:07 +00:00
public long TotalScore { get; set; }
2018-04-13 09:19:50 +00:00
[Column(TypeName = "DECIMAL(1,4)")] // TODO: This data type is wrong (should contain more precision). But at the same time, we probably don't need to be storing this in the database.
2018-04-13 09:19:50 +00:00
public double Accuracy { get; set; }
2021-07-23 20:37:08 +00:00
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
2018-04-13 09:19:50 +00:00
public double? PP { get; set; }
public int MaxCombo { get; set; }
2018-12-14 12:09:17 +00:00
public int Combo { get; set; } // Todo: Shouldn't exist in here
2018-04-13 09:19:50 +00:00
2018-11-28 08:26:46 +00:00
public int RulesetID { get; set; }
2018-12-14 12:09:17 +00:00
[NotMapped]
public bool Passed { get; set; } = true;
2021-10-28 08:57:17 +00:00
public RulesetInfo Ruleset { get; set; }
2018-04-13 09:19:50 +00:00
private APIMod[] localAPIMods;
2021-10-28 09:23:52 +00:00
2018-11-30 07:11:09 +00:00
private Mod[] mods;
[NotMapped]
2018-11-28 10:47:20 +00:00
public Mod[] Mods
2018-11-28 07:39:08 +00:00
{
2018-11-28 10:47:20 +00:00
get
2018-11-28 07:39:08 +00:00
{
2021-04-22 09:44:14 +00:00
var rulesetInstance = Ruleset?.CreateInstance();
if (rulesetInstance == null)
return mods ?? Array.Empty<Mod>();
2021-04-21 06:16:28 +00:00
2021-04-22 09:44:14 +00:00
Mod[] scoreMods = Array.Empty<Mod>();
2021-04-21 06:16:28 +00:00
2018-11-30 09:52:31 +00:00
if (mods != null)
2021-04-21 06:16:28 +00:00
scoreMods = mods;
else if (localAPIMods != null)
scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
2018-11-30 07:11:09 +00:00
2021-04-21 06:16:28 +00:00
return scoreMods;
2018-11-30 07:11:09 +00:00
}
set
{
localAPIMods = null;
2018-11-30 07:11:09 +00:00
mods = value;
2018-11-28 07:39:08 +00:00
}
}
// Used for API serialisation/deserialisation.
2021-04-12 11:49:44 +00:00
[NotMapped]
public APIMod[] APIMods
2018-11-30 07:11:09 +00:00
{
get
{
if (localAPIMods != null)
return localAPIMods;
if (mods == null)
return Array.Empty<APIMod>();
return localAPIMods = mods.Select(m => new APIMod(m)).ToArray();
}
2018-11-30 07:11:09 +00:00
set
{
localAPIMods = value;
2018-11-30 07:11:09 +00:00
// We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary.
2018-11-30 07:11:09 +00:00
mods = null;
}
}
2018-11-28 10:47:20 +00:00
// Used for database serialisation/deserialisation.
[Column("Mods")]
2021-04-12 11:50:18 +00:00
public string ModsJson
{
get => JsonConvert.SerializeObject(APIMods);
set => APIMods = JsonConvert.DeserializeObject<APIMod[]>(value);
}
2018-12-22 06:17:35 +00:00
[NotMapped]
public APIUser User { get; set; }
2018-04-13 09:19:50 +00:00
2018-11-30 07:11:09 +00:00
[Column("User")]
2018-11-28 07:39:08 +00:00
public string UserString
{
get => User?.Username;
set
{
User ??= new APIUser();
2019-02-25 06:25:22 +00:00
User.Username = value;
}
}
[Column("UserID")]
public int? UserID
{
2019-02-25 05:40:44 +00:00
get => User?.Id ?? 1;
set
{
User ??= new APIUser();
2019-02-25 06:25:22 +00:00
User.Id = value ?? 1;
}
2018-11-28 07:39:08 +00:00
}
public int BeatmapInfoID { get; set; }
[Column("Beatmap")]
public BeatmapInfo BeatmapInfo { get; set; }
2018-04-13 09:19:50 +00:00
private long? onlineID;
[Column("OnlineScoreID")]
public long? OnlineID
{
get => onlineID;
set => onlineID = value > 0 ? value : null;
}
2018-04-13 09:19:50 +00:00
2018-11-28 11:16:20 +00:00
public DateTimeOffset Date { get; set; }
2018-04-13 09:19:50 +00:00
[NotMapped]
2021-10-28 09:23:52 +00:00
public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
2018-11-28 07:39:08 +00:00
2018-11-30 07:11:09 +00:00
[Column("Statistics")]
public string StatisticsJson
{
get => JsonConvert.SerializeObject(Statistics);
set
{
if (value == null)
2018-11-30 07:11:09 +00:00
{
Statistics.Clear();
return;
2018-11-30 07:11:09 +00:00
}
2018-12-05 10:44:01 +00:00
Statistics = JsonConvert.DeserializeObject<Dictionary<HitResult, int>>(value);
}
}
[NotMapped]
2020-06-19 10:58:35 +00:00
public List<HitEvent> HitEvents { get; set; }
public List<ScoreFileInfo> Files { get; } = new List<ScoreFileInfo>();
2018-11-28 07:39:08 +00:00
public string Hash { get; set; }
2018-11-28 07:39:08 +00:00
public bool DeletePending { get; set; }
2018-11-30 07:11:09 +00:00
/// <summary>
/// The position of this score, starting at 1.
/// </summary>
[NotMapped]
public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone.
/// <summary>
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
/// </summary>
[NotMapped]
2021-06-08 09:23:03 +00:00
public bool IsLegacyScore => Mods.OfType<ModClassic>().Any();
2020-10-07 06:34:03 +00:00
public IEnumerable<HitResultDisplayStatistic> GetStatisticsForDisplay()
{
2020-10-07 06:34:03 +00:00
foreach (var r in Ruleset.CreateInstance().GetHitResults())
{
int value = Statistics.GetValueOrDefault(r.result);
2020-10-07 06:34:03 +00:00
switch (r.result)
{
case HitResult.SmallTickHit:
{
int total = value + Statistics.GetValueOrDefault(HitResult.SmallTickMiss);
if (total > 0)
2020-10-07 06:34:03 +00:00
yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName);
break;
}
case HitResult.LargeTickHit:
{
int total = value + Statistics.GetValueOrDefault(HitResult.LargeTickMiss);
if (total > 0)
2020-10-07 06:34:03 +00:00
yield return new HitResultDisplayStatistic(r.result, value, total, r.displayName);
break;
}
case HitResult.SmallTickMiss:
case HitResult.LargeTickMiss:
break;
default:
2020-10-07 06:34:03 +00:00
yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName);
break;
}
}
}
public ScoreInfo DeepClone()
{
var clone = (ScoreInfo)MemberwiseClone();
clone.Statistics = new Dictionary<HitResult, int>(clone.Statistics);
return clone;
}
public override string ToString() => this.GetDisplayTitle();
2019-12-03 04:33:42 +00:00
public bool Equals(ScoreInfo other)
{
if (ReferenceEquals(this, other)) return true;
if (other == null) return false;
2019-12-03 04:33:42 +00:00
if (ID != 0 && other.ID != 0)
return ID == other.ID;
return false;
2019-12-03 04:33:42 +00:00
}
#region Implementation of IHasOnlineID
long IHasOnlineID<long>.OnlineID => OnlineID ?? -1;
2021-10-28 09:23:52 +00:00
#endregion
#region Implementation of IScoreInfo
2021-10-28 09:23:52 +00:00
IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo;
IRulesetInfo IScoreInfo.Ruleset => Ruleset;
IUser IScoreInfo.User => User;
2021-10-28 09:23:52 +00:00
bool IScoreInfo.HasReplay => Files.Any();
#endregion
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => Files;
2018-04-13 09:19:50 +00:00
}
}