UI: Add descriptions of what each dynamic RPC formatter actually shows when hovering whether it has support in the game info popup

This commit is contained in:
Evan Husted 2025-02-15 20:45:27 -06:00
parent aa2178dbe5
commit 0d7d0e8092
6 changed files with 71 additions and 18 deletions

View File

@ -119,17 +119,23 @@
TextWrapping="Wrap" >
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="5">
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
<StackPanel Orientation="Horizontal" Spacing="5" ToolTip.Tip="{Binding DynamicRichPresenceDescription}">
<ui:SymbolIcon
Foreground="ForestGreen"
Symbol="Checkmark"
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock
Foreground="ForestGreen"
HorizontalAlignment="Stretch"
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
Text="{ext:Locale GameInfoRpcDynamic}"
TextAlignment="Start"
TextWrapping="Wrap" >
TextWrapping="Wrap">
</TextBlock>
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
<ui:SymbolIcon
Foreground="Red"
Symbol="Cancel"
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
<TextBlock
Foreground="Red"
HorizontalAlignment="Stretch"

View File

@ -1,6 +1,7 @@
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.PlayReport;
namespace Ryujinx.Ava.UI.ViewModels
{
@ -10,6 +11,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
public string DynamicRichPresenceDescription =>
AppData.HasDynamicRichPresenceSupport
? AppData.RichPresenceSpec.Value.Description
: GameSpec.DefaultDescription;
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);

View File

@ -10,6 +10,7 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.Compat;
using Ryujinx.Ava.Utilities.PlayReport;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
@ -35,9 +36,14 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
{
_id = value;
Compatibility = CompatibilityCsv.Find(Id);
Compatibility = CompatibilityCsv.Find(value);
RichPresenceSpec = PlayReports.Analyzer.TryGetSpec(IdString, out GameSpec gameSpec)
? gameSpec
: default(Optional<GameSpec>);
}
}
public Optional<GameSpec> RichPresenceSpec { get; set; }
public string Developer { get; set; } = "Unknown";
public string Version { get; set; } = "0";
public int PlayerCount { get; set; }
@ -46,7 +52,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
public bool HasDynamicRichPresenceSupport => RichPresenceSpec.HasValue;
public TimeSpan TimePlayed { get; set; }
public DateTime? LastPlayed { get; set; }

View File

@ -20,6 +20,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
public GameSpec GetSpec(string titleId) => _specs.First(x => x.TitleIds.ContainsIgnoreCase(titleId));
public bool TryGetSpec(string titleId, out GameSpec gameSpec)
=> (gameSpec = _specs.FirstOrDefault(x => x.TitleIds.ContainsIgnoreCase(titleId))) != null;
/// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
/// </summary>
@ -128,13 +133,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
{
if (!playReport.ReportData.IsDictionary)
return FormattedValue.Unhandled;
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
if (!TryGetSpec(runningGameId, out GameSpec spec))
return FormattedValue.Unhandled;
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
{
if (!formatSpec.Format(appMeta, playReport, out FormattedValue value))
if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value))
continue;
return value;

View File

@ -17,6 +17,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
.AddSpec(
"01007ef00011e000",
spec => spec
.WithDescription("based on being in Master Mode.")
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
@ -24,34 +25,48 @@ namespace Ryujinx.Ava.Utilities.PlayReport
.AddSpec(
"0100f2c0115b6000",
spec => spec
.WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).")
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
.AddSpec(
"01002da013484000",
spec => spec
.WithDescription("based on how many Rupees you have.")
.AddValueFormatter("rupees", SkywardSwordHD_Rupees))
.AddSpec(
"0100000000010000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
spec => spec
.WithDescription("based on if you're playing with Assist Mode.")
.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
)
.AddSpec(
"010075000ecbe000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
spec => spec
.WithDescription("based on if you're playing with Assist Mode.")
.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
)
.AddSpec(
"010028600ebda000",
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
spec => spec
.WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.")
.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
)
.AddSpec( // Global & China IDs
["0100152000022000", "010075100e8ec000"],
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
spec => spec
.WithDescription("based on what modes you're selecting in the menu & whether or not you're in a race.")
.AddValueFormatter("To", MarioKart8Deluxe_Mode)
)
.AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec
.WithDescription("based on what area of Paldea you're exploring.")
.AddValueFormatter("area_no", PokemonSVArea)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
)
.AddSpec(
"01006a800016e000",
spec => spec
.WithDescription("based on what mode you're playing, who won, and what characters were present.")
.AddSparseMultiValueFormatter(
[
// Metadata to figure out what PlayReport we have.
@ -72,9 +87,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
"010012f017576000", "0100c62011050000", "0100b3c014bda000"
],
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
spec => spec
.WithDescription("based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.")
.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
)
.AddSpec("01002da013484000", spec => spec.AddValueFormatter("rupees", SkywardSwordHD_Rupees))
);
private static string Playing(string game) => $"Playing {game}";

View File

@ -23,6 +23,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport
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<FormatterSpecBase> ValueFormatters { get; } = [];
@ -197,7 +211,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
public string[] ReportKeys { get; init; }
public Delegate Formatter { get; init; }
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
out FormattedValue formattedValue)
{
formattedValue = default;