using MsgPack; using Ryujinx.Ava.Systems.AppLibrary; using System; using System.Collections.Generic; using System.Linq; namespace Ryujinx.Ava.Systems.PlayReport { /// /// A mapping of title IDs to value formatter specs. /// /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself. /// public class GameSpec { public static GameSpec Create(string requiredTitleId, params IEnumerable otherTitleIds) => new() { TitleIds = otherTitleIds.Prepend(requiredTitleId).ToArray() }; public static GameSpec Create(IEnumerable titleIds) => new() { TitleIds = titleIds.ToArray() }; private int _lastPriority; public required string[] TitleIds { get; init; } public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game."; private string _valueDescription; public string Description => _valueDescription ?? DefaultDescription; public GameSpec WithDescription(string description) { _valueDescription = description != null ? $"Formats the details on your Discord presence {description}" : null; return this; } public List ValueFormatters { get; } = []; /// /// Add a value formatter to the current /// matching a specific key that could exist in a Play Report for the previously specified title IDs. /// /// The key name to match. /// The function which can return a potential formatted value. /// The current , for chaining convenience. public GameSpec AddValueFormatter( string reportKey, SingleValueFormatter valueFormatter ) => AddValueFormatter(_lastPriority++, reportKey, valueFormatter); /// /// Add a value formatter at a specific priority to the current /// matching a specific key that could exist in a Play Report for the previously specified title IDs. /// /// The resolution priority of this value formatter. Higher resolves sooner. /// The key name to match. /// The function which can return a potential formatted value. /// The current , for chaining convenience. public GameSpec AddValueFormatter( int priority, string reportKey, SingleValueFormatter valueFormatter ) => AddValueFormatter(new FormatterSpec { Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter }); /// /// Add a multi-value formatter to the current /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. /// /// The key names to match. /// The function which can format the values. /// The current , for chaining convenience. public GameSpec AddMultiValueFormatter( string[] reportKeys, MultiValueFormatter valueFormatter ) => AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter); /// /// Add a multi-value formatter at a specific priority to the current /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. /// /// The resolution priority of this value formatter. Higher resolves sooner. /// The key names to match. /// The function which can format the values. /// The current , for chaining convenience. public GameSpec AddMultiValueFormatter( int priority, string[] reportKeys, MultiValueFormatter valueFormatter ) => AddValueFormatter(new MultiFormatterSpec { Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter }); /// /// Add a multi-value formatter to the current /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. ///

/// The 'Sparse' multi-value formatters do not require every key to be present. /// If you need this requirement, use . ///
/// The key names to match. /// The function which can format the values. /// The current , for chaining convenience. public GameSpec AddSparseMultiValueFormatter( string[] reportKeys, SparseMultiValueFormatter valueFormatter ) => AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter); /// /// Add a multi-value formatter at a specific priority to the current /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. ///

/// The 'Sparse' multi-value formatters do not require every key to be present. /// If you need this requirement, use . ///
/// The resolution priority of this value formatter. Higher resolves sooner. /// The key names to match. /// The function which can format the values. /// The current , for chaining convenience. public GameSpec AddSparseMultiValueFormatter( int priority, string[] reportKeys, SparseMultiValueFormatter valueFormatter ) => AddValueFormatter(new SparseMultiFormatterSpec { Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter }); private GameSpec AddValueFormatter(T formatterSpec) where T : FormatterSpecBase { ValueFormatters.Add(formatterSpec); return this; } } /// /// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value. /// public class FormatterSpec : FormatterSpecBase { public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result) { if (!playReport.ReportData.AsDictionary().TryGetValue(ReportKeys[0], out MessagePackObject valuePackObject)) { result = null; return false; } result = valuePackObject; return true; } } /// /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values. /// public class MultiFormatterSpec : FormatterSpecBase { public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result) { List packedObjects = []; foreach (string reportKey in ReportKeys) { if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) { result = null; return false; } packedObjects.Add(valuePackObject); } result = packedObjects; return true; } } /// /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their sparsely populated potential values. /// public class SparseMultiFormatterSpec : FormatterSpecBase { public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result) { Dictionary packedObjects = []; foreach (string reportKey in ReportKeys) { if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) continue; packedObjects.Add(reportKey, valuePackObject); } result = packedObjects; return true; } } public abstract class FormatterSpecBase { public abstract bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object data); public int Priority { get; init; } public string[] ReportKeys { get; init; } public Delegate Formatter { get; init; } public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, out FormattedValue formattedValue) { formattedValue = default; if (!GetData(playReport, out object data)) return false; if (data is FormattedValue fv) { formattedValue = fv; return true; } switch (Formatter) { case SingleValueFormatter svf when data is MessagePackObject match: formattedValue = svf( new SingleValue(match) { Application = appMeta, PlayReport = playReport } ); return true; case MultiValueFormatter mvf when data is List matches: formattedValue = mvf( new MultiValue(matches) { Application = appMeta, PlayReport = playReport } ); return true; case SparseMultiValueFormatter smvf when data is Dictionary sparseMatches: formattedValue = smvf( new SparseMultiValue(sparseMatches) { Application = appMeta, PlayReport = playReport } ); return true; default: throw new InvalidOperationException("Formatter delegate is not of a known type!"); } } } }