UI: The argument to Play Report value formatters is now a struct containing the current ApplicationMetadata & the BoxedValue that was the only argument previously.

This allows for the title of Mario Kart to be localized when one of the value checkers doesn't match.
metal Canary-1.2.329
Evan Husted 2025-02-02 20:47:42 -06:00
parent 8117e160c2
commit fe43c32e60
3 changed files with 112 additions and 94 deletions

View File

@ -1,80 +0,0 @@
using Gommon;
using MsgPack;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Common.Helper
{
public class PlayReportAnalyzer
{
private readonly List<PlayReportGameSpec> _specs = [];
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
{
_specs.Add(transform(new PlayReportGameSpec { TitleIdStr = titleId }));
return this;
}
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
{
_specs.Add(new PlayReportGameSpec { TitleIdStr = titleId }.Apply(transform));
return this;
}
public Optional<string> Run(string runningGameId, MessagePackObject playReport)
{
if (!playReport.IsDictionary)
return Optional<string>.None;
if (!_specs.TryGetFirst(s => s.TitleIdStr.EqualsIgnoreCase(runningGameId), out PlayReportGameSpec spec))
return Optional<string>.None;
foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority))
{
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue;
return formatSpec.ValueFormatter(valuePackObject.ToObject());
}
return Optional<string>.None;
}
}
public class PlayReportGameSpec
{
public required string TitleIdStr { get; init; }
public List<PlayReportValueFormatterSpec> Analyses { get; } = [];
public PlayReportGameSpec AddValueFormatter(string reportKey, Func<object, string> valueFormatter)
{
Analyses.Add(new PlayReportValueFormatterSpec
{
Priority = Analyses.Count,
ReportKey = reportKey,
ValueFormatter = valueFormatter
});
return this;
}
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, Func<object, string> valueFormatter)
{
Analyses.Add(new PlayReportValueFormatterSpec
{
Priority = priority,
ReportKey = reportKey,
ValueFormatter = valueFormatter
});
return this;
}
}
public struct PlayReportValueFormatterSpec
{
public required int Priority { get; init; }
public required string ReportKey { get; init; }
public required Func<object, string> ValueFormatter { get; init; }
}
}

View File

@ -39,6 +39,7 @@ namespace Ryujinx.Ava
private static DiscordRpcClient _discordClient;
private static RichPresence _discordPresenceMain;
private static RichPresence _discordPresencePlaying;
private static ApplicationMetadata _currentApp;
public static void Initialize()
{
@ -113,6 +114,7 @@ namespace Ryujinx.Ava
private static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
{
_discordClient?.SetPresence(_discordPresencePlaying ??= CreatePlayingState(appMeta, procRes));
_currentApp = appMeta;
}
private static void UpdatePlayingState()
@ -124,6 +126,7 @@ namespace Ryujinx.Ava
{
_discordClient?.SetPresence(_discordPresenceMain);
_discordPresencePlaying = null;
_currentApp = null;
}
private static void HandlePlayReport(MessagePackObject playReport)
@ -131,7 +134,7 @@ namespace Ryujinx.Ava
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return;
Optional<string> details = PlayReport.Analyzer.Run(TitleIDs.CurrentApplication.Value, playReport);
Optional<string> details = PlayReport.Analyzer.Run(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
if (!details.HasValue) return;

View File

@ -1,4 +1,10 @@
using Ryujinx.Common.Helper;
using Gommon;
using MsgPack;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Common.Helper;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Utilities
{
@ -32,20 +38,20 @@ namespace Ryujinx.Ava.Utilities
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
);
private static string BreathOfTheWild_MasterMode(object val)
=> val is 1 ? "Playing Master Mode" : "Playing Normal Mode";
private static string BreathOfTheWild_MasterMode(ref PlayReportValue value)
=> value.BoxedValue is 1 ? "Playing Master Mode" : "Playing Normal Mode";
private static string SuperMarioOdyssey_AssistMode(object val)
=> val is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
private static string SuperMarioOdyssey_AssistMode(ref PlayReportValue value)
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
private static string SuperMarioOdysseyChina_AssistMode(object val)
=> val is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
private static string SuperMarioOdysseyChina_AssistMode(ref PlayReportValue value)
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
private static string SuperMario3DWorldOrBowsersFury(object val)
=> val is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
private static string SuperMario3DWorldOrBowsersFury(ref PlayReportValue value)
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
private static string MarioKart8Deluxe_Mode(object obj)
=> obj switch
private static string MarioKart8Deluxe_Mode(ref PlayReportValue value)
=> value.BoxedValue switch
{
// Single Player
"Single" => "Single Player",
@ -69,8 +75,97 @@ namespace Ryujinx.Ava.Utilities
"Battle" => "Battle Mode",
"RaceStart" => "Selecting a Course",
"Race" => "Racing",
//TODO: refactor value formatting system to pass in the name from the content archive so this can be localized properly
_ => "Playing Mario Kart 8 Deluxe"
_ => $"Playing {value.Application.Title}"
};
}
#region Analyzer implementation
public class PlayReportAnalyzer
{
private readonly List<PlayReportGameSpec> _specs = [];
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
{
_specs.Add(transform(new PlayReportGameSpec { TitleIdStr = titleId }));
return this;
}
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
{
_specs.Add(new PlayReportGameSpec { TitleIdStr = titleId }.Apply(transform));
return this;
}
public Optional<string> Run(string runningGameId, ApplicationMetadata appMeta, MessagePackObject playReport)
{
if (!playReport.IsDictionary)
return Optional<string>.None;
if (!_specs.TryGetFirst(s => s.TitleIdStr.EqualsIgnoreCase(runningGameId), out PlayReportGameSpec spec))
return Optional<string>.None;
foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority))
{
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue;
PlayReportValue value = new()
{
Application = appMeta,
BoxedValue = valuePackObject.ToObject()
};
return formatSpec.ValueFormatter(ref value);
}
return Optional<string>.None;
}
}
public class PlayReportGameSpec
{
public required string TitleIdStr { get; init; }
public List<PlayReportValueFormatterSpec> Analyses { get; } = [];
public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter)
{
Analyses.Add(new PlayReportValueFormatterSpec
{
Priority = Analyses.Count,
ReportKey = reportKey,
ValueFormatter = valueFormatter
});
return this;
}
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, PlayReportValueFormatter valueFormatter)
{
Analyses.Add(new PlayReportValueFormatterSpec
{
Priority = priority,
ReportKey = reportKey,
ValueFormatter = valueFormatter
});
return this;
}
}
public struct PlayReportValue
{
public ApplicationMetadata Application { get; init; }
public object BoxedValue { get; init; }
}
public struct PlayReportValueFormatterSpec
{
public required int Priority { get; init; }
public required string ReportKey { get; init; }
public required PlayReportValueFormatter ValueFormatter { get; init; }
}
public delegate string PlayReportValueFormatter(ref PlayReportValue value);
#endregion
}