diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json
index a0b899fd5..5657bb88e 100644
--- a/src/Ryujinx/Assets/locales.json
+++ b/src/Ryujinx/Assets/locales.json
@@ -23723,4 +23723,4 @@
}
}
]
-}
+}
\ No newline at end of file
diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs
index 20b296511..d95bb80dd 100644
--- a/src/Ryujinx/DiscordIntegrationModule.cs
+++ b/src/Ryujinx/DiscordIntegrationModule.cs
@@ -4,16 +4,12 @@ using MsgPack;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration;
+using Ryujinx.Ava.Utilities.PlayReport;
using Ryujinx.Common;
-using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
using System.Text;
namespace Ryujinx.Ava
@@ -130,8 +126,8 @@ namespace Ryujinx.Ava
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return;
- PlayReportAnalyzer.FormattedValue formattedValue =
- PlayReport.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
+ Analyzer.FormattedValue formattedValue =
+ PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
if (!formattedValue.Handled) return;
diff --git a/src/Ryujinx/Utilities/PlayReport.cs b/src/Ryujinx/Utilities/PlayReport.cs
deleted file mode 100644
index f518fb902..000000000
--- a/src/Ryujinx/Utilities/PlayReport.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using PlayReportFormattedValue = Ryujinx.Ava.Utilities.PlayReportAnalyzer.FormattedValue;
-
-namespace Ryujinx.Ava.Utilities
-{
- public static class PlayReport
- {
- public static PlayReportAnalyzer Analyzer { get; } = new PlayReportAnalyzer()
- .AddSpec(
- "01007ef00011e000",
- spec => spec
- .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
- // reset to normal status when switching between normal & master mode in title screen
- .AddValueFormatter("AoCVer", PlayReportFormattedValue.AlwaysResets)
- )
- .AddSpec(
- "0100f2c0115b6000",
- spec => spec.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
- .AddSpec(
- "0100000000010000",
- spec =>
- spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
- )
- .AddSpec(
- "010075000ecbe000",
- spec =>
- spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
- )
- .AddSpec(
- "010028600ebda000",
- spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
- )
- .AddSpec( // Global & China IDs
- ["0100152000022000", "010075100e8ec000"],
- spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
- );
-
- private static PlayReportFormattedValue BreathOfTheWild_MasterMode(PlayReportValue value)
- => value.BoxedValue is 1 ? "Playing Master Mode" : PlayReportFormattedValue.ForceReset;
-
- private static PlayReportFormattedValue TearsOfTheKingdom_CurrentField(PlayReportValue value) =>
- value.DoubleValue switch
- {
- > 800d => "Exploring the Sky Islands",
- < -201d => "Exploring the Depths",
- _ => "Roaming Hyrule"
- };
-
- private static PlayReportFormattedValue SuperMarioOdyssey_AssistMode(PlayReportValue value)
- => value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
-
- private static PlayReportFormattedValue SuperMarioOdysseyChina_AssistMode(PlayReportValue value)
- => value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
-
- private static PlayReportFormattedValue SuperMario3DWorldOrBowsersFury(PlayReportValue value)
- => value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
-
- private static PlayReportFormattedValue MarioKart8Deluxe_Mode(PlayReportValue value)
- => value.StringValue switch
- {
- // Single Player
- "Single" => "Single Player",
- // Multiplayer
- "Multi-2players" => "Multiplayer 2 Players",
- "Multi-3players" => "Multiplayer 3 Players",
- "Multi-4players" => "Multiplayer 4 Players",
- // Wireless/LAN Play
- "Local-Single" => "Wireless/LAN Play",
- "Local-2players" => "Wireless/LAN Play 2 Players",
- // CC Classes
- "50cc" => "50cc",
- "100cc" => "100cc",
- "150cc" => "150cc",
- "Mirror" => "Mirror (150cc)",
- "200cc" => "200cc",
- // Modes
- "GrandPrix" => "Grand Prix",
- "TimeAttack" => "Time Trials",
- "VS" => "VS Races",
- "Battle" => "Battle Mode",
- "RaceStart" => "Selecting a Course",
- "Race" => "Racing",
- _ => PlayReportFormattedValue.ForceReset
- };
- }
-}
diff --git a/src/Ryujinx/Utilities/PlayReportAnalyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs
similarity index 59%
rename from src/Ryujinx/Utilities/PlayReportAnalyzer.cs
rename to src/Ryujinx/Utilities/PlayReport/Analyzer.cs
index 47c36a396..84bdbf085 100644
--- a/src/Ryujinx/Utilities/PlayReportAnalyzer.cs
+++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs
@@ -6,27 +6,27 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-namespace Ryujinx.Ava.Utilities
+namespace Ryujinx.Ava.Utilities.PlayReport
{
///
/// The entrypoint for the Play Report analysis system.
///
- public class PlayReportAnalyzer
+ public class Analyzer
{
- private readonly List _specs = [];
+ private readonly List _specs = [];
///
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
///
/// The ID of the game to listen to Play Reports in.
/// The configuration function for the analysis spec.
- /// The current , for chaining convenience.
- public PlayReportAnalyzer AddSpec(string titleId, Func transform)
+ /// The current , for chaining convenience.
+ public Analyzer AddSpec(string titleId, Func transform)
{
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
- $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
+ $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
- _specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] }));
+ _specs.Add(transform(new GameSpec { TitleIds = [titleId] }));
return this;
}
@@ -35,13 +35,13 @@ namespace Ryujinx.Ava.Utilities
///
/// The ID of the game to listen to Play Reports in.
/// The configuration function for the analysis spec.
- /// The current , for chaining convenience.
- public PlayReportAnalyzer AddSpec(string titleId, Action transform)
+ /// The current , for chaining convenience.
+ public Analyzer AddSpec(string titleId, Action transform)
{
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
- $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
+ $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
- _specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform));
+ _specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform));
return this;
}
@@ -50,15 +50,15 @@ namespace Ryujinx.Ava.Utilities
///
/// The IDs of the games to listen to Play Reports in.
/// The configuration function for the analysis spec.
- /// The current , for chaining convenience.
- public PlayReportAnalyzer AddSpec(IEnumerable titleIds,
- Func transform)
+ /// The current , for chaining convenience.
+ public Analyzer AddSpec(IEnumerable titleIds,
+ Func transform)
{
string[] tids = titleIds.ToArray();
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
- $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
+ $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
- _specs.Add(transform(new PlayReportGameSpec { TitleIds = [..tids] }));
+ _specs.Add(transform(new GameSpec { TitleIds = [..tids] }));
return this;
}
@@ -67,20 +67,20 @@ namespace Ryujinx.Ava.Utilities
///
/// The IDs of the games to listen to Play Reports in.
/// The configuration function for the analysis spec.
- /// The current , for chaining convenience.
- public PlayReportAnalyzer AddSpec(IEnumerable titleIds, Action transform)
+ /// The current , for chaining convenience.
+ public Analyzer AddSpec(IEnumerable titleIds, Action transform)
{
string[] tids = titleIds.ToArray();
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
- $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}.");
+ $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
- _specs.Add(new PlayReportGameSpec { TitleIds = [..tids] }.Apply(transform));
+ _specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform));
return this;
}
///
- /// Runs the configured for the specified game title ID.
+ /// Runs the configured for the specified game title ID.
///
/// The game currently running.
/// The Application metadata information, including localized game name and play time information.
@@ -95,25 +95,44 @@ namespace Ryujinx.Ava.Utilities
if (!playReport.IsDictionary)
return FormattedValue.Unhandled;
- if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec))
+ if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
return FormattedValue.Unhandled;
- foreach (PlayReportGameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
+ foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
{
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue;
- return formatSpec.ValueFormatter(new PlayReportValue
+ return formatSpec.ValueFormatter(new Value
{
Application = appMeta, PackedValue = valuePackObject
});
}
+
+ foreach (GameSpec.MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
+ {
+ List packedObjects = [];
+ foreach (var reportKey in formatSpec.ReportKeys)
+ {
+ if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
+ continue;
+
+ packedObjects.Add(valuePackObject);
+ }
+
+ if (packedObjects.Count != formatSpec.ReportKeys.Length)
+ return FormattedValue.Unhandled;
+
+ return formatSpec.ValueFormatter(packedObjects
+ .Select(packObject => new Value { Application = appMeta, PackedValue = packObject })
+ .ToArray());
+ }
return FormattedValue.Unhandled;
}
///
- /// A potential formatted value returned by a .
+ /// A potential formatted value returned by a .
///
public readonly struct FormattedValue
{
@@ -123,7 +142,7 @@ namespace Ryujinx.Ava.Utilities
public bool Handled { get; private init; }
///
- /// Did the handler request the caller of the to reset the existing value?
+ /// Did the handler request the caller of the to reset the existing value?
///
public bool Reset { get; private init; }
@@ -151,42 +170,43 @@ namespace Ryujinx.Ava.Utilities
public static FormattedValue Unhandled => default;
///
- /// Return this to suggest the caller reset the value it's using the for.
+ /// Return this to suggest the caller reset the value it's using the for.
///
public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
///
- /// A delegate singleton you can use to always return in a .
+ /// A delegate singleton you can use to always return in a .
///
- public static readonly PlayReportValueFormatter AlwaysResets = _ => ForceReset;
+ public static readonly ValueFormatter AlwaysResets = _ => ForceReset;
///
/// A delegate factory you can use to always return the specified
- /// in a .
+ /// in a .
///
/// The string to always return for this delegate instance.
- public static PlayReportValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
+ public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
}
}
///
/// A mapping of title IDs to value formatter specs.
///
- /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself.
+ /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself.
///
- public class PlayReportGameSpec
+ public class GameSpec
{
public required string[] TitleIds { get; init; }
public List SimpleValueFormatters { get; } = [];
+ public List MultiValueFormatters { get; } = [];
///
- /// Add a value formatter to the current
+ /// 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 PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter)
+ /// The current , for chaining convenience.
+ public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
{
SimpleValueFormatters.Add(new FormatterSpec
{
@@ -196,15 +216,15 @@ namespace Ryujinx.Ava.Utilities
}
///
- /// Add a value formatter at a specific priority to the current
+ /// 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 PlayReportGameSpec AddValueFormatter(int priority, string reportKey,
- PlayReportValueFormatter valueFormatter)
+ /// The current , for chaining convenience.
+ public GameSpec AddValueFormatter(int priority, string reportKey,
+ ValueFormatter valueFormatter)
{
SimpleValueFormatters.Add(new FormatterSpec
{
@@ -212,6 +232,40 @@ namespace Ryujinx.Ava.Utilities
});
return this;
}
+
+ ///
+ /// 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)
+ {
+ MultiValueFormatters.Add(new MultiFormatterSpec
+ {
+ Priority = SimpleValueFormatters.Count, ReportKeys = reportKeys, ValueFormatter = valueFormatter
+ });
+ return this;
+ }
+
+ ///
+ /// 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)
+ {
+ MultiValueFormatters.Add(new MultiFormatterSpec
+ {
+ Priority = priority, ReportKeys = reportKeys, ValueFormatter = valueFormatter
+ });
+ return this;
+ }
///
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
@@ -220,16 +274,26 @@ namespace Ryujinx.Ava.Utilities
{
public required int Priority { get; init; }
public required string ReportKey { get; init; }
- public PlayReportValueFormatter ValueFormatter { get; init; }
+ public ValueFormatter ValueFormatter { get; init; }
+ }
+
+ ///
+ /// 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 struct MultiFormatterSpec
+ {
+ public required int Priority { get; init; }
+ public required string[] ReportKeys { get; init; }
+ public MultiValueFormatter ValueFormatter { get; init; }
}
}
///
- /// The input data to a ,
+ /// The input data to a ,
/// containing the currently running application's ,
/// and the matched from the Play Report.
///
- public class PlayReportValue
+ public class Value
{
///
/// The currently running application's .
@@ -245,7 +309,7 @@ namespace Ryujinx.Ava.Utilities
/// Access the as its underlying .NET type.
///
/// Does not seem to work well with comparing numeric types,
- /// so use and the AsX (where X is a numerical type name i.e. Int32) methods for that.
+ /// so use XValue properties for that.
///
public object BoxedValue => PackedValue.ToObject();
@@ -269,14 +333,26 @@ namespace Ryujinx.Ava.Utilities
}
///
- /// The delegate type that powers the entire analysis system (as it currently is).
+ /// The delegate type that powers single value formatters.
/// Takes in the result value from the Play Report, and outputs:
///
/// a formatted string,
///
/// a signal that nothing was available to handle it,
///
- /// OR a signal to reset the value that the caller is using the for.
+ /// OR a signal to reset the value that the caller is using the for.
///
- public delegate PlayReportAnalyzer.FormattedValue PlayReportValueFormatter(PlayReportValue value);
+ public delegate Analyzer.FormattedValue ValueFormatter(Value value);
+
+ ///
+ /// The delegate type that powers multiple value formatters.
+ /// Takes in the result value from the Play Report, and outputs:
+ ///
+ /// a formatted string,
+ ///
+ /// a signal that nothing was available to handle it,
+ ///
+ /// OR a signal to reset the value that the caller is using the for.
+ ///
+ public delegate Analyzer.FormattedValue MultiValueFormatter(Value[] value);
}
diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs
new file mode 100644
index 000000000..25457744e
--- /dev/null
+++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs
@@ -0,0 +1,129 @@
+using static Ryujinx.Ava.Utilities.PlayReport.Analyzer;
+
+namespace Ryujinx.Ava.Utilities.PlayReport
+{
+ public static class PlayReports
+ {
+ public static Analyzer Analyzer { get; } = new Analyzer()
+ .AddSpec(
+ "01007ef00011e000",
+ spec => spec
+ .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
+ // reset to normal status when switching between normal & master mode in title screen
+ .AddValueFormatter("AoCVer", FormattedValue.AlwaysResets)
+ )
+ .AddSpec(
+ "0100f2c0115b6000",
+ spec => spec
+ .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
+ .AddSpec(
+ "0100000000010000",
+ spec =>
+ spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
+ )
+ .AddSpec(
+ "010075000ecbe000",
+ spec =>
+ spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
+ )
+ .AddSpec(
+ "010028600ebda000",
+ spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
+ )
+ .AddSpec( // Global & China IDs
+ ["0100152000022000", "010075100e8ec000"],
+ spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
+ )
+ .AddSpec(
+ ["0100a3d008c5c000", "01008f6008c5e000"],
+ spec => spec
+ .AddValueFormatter("area_no", PokemonSVArea)
+ .AddValueFormatter("team_circle", PokemonSVUnionCircle)
+ );
+
+ private static FormattedValue BreathOfTheWild_MasterMode(Value value)
+ => value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
+
+ private static FormattedValue TearsOfTheKingdom_CurrentField(Value value) =>
+ value.DoubleValue switch
+ {
+ > 800d => "Exploring the Sky Islands",
+ < -201d => "Exploring the Depths",
+ _ => "Roaming Hyrule"
+ };
+
+ private static FormattedValue SuperMarioOdyssey_AssistMode(Value value)
+ => value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
+
+ private static FormattedValue SuperMarioOdysseyChina_AssistMode(Value value)
+ => value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
+
+ private static FormattedValue SuperMario3DWorldOrBowsersFury(Value value)
+ => value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
+
+ private static FormattedValue MarioKart8Deluxe_Mode(Value value)
+ => value.StringValue switch
+ {
+ // Single Player
+ "Single" => "Single Player",
+ // Multiplayer
+ "Multi-2players" => "Multiplayer 2 Players",
+ "Multi-3players" => "Multiplayer 3 Players",
+ "Multi-4players" => "Multiplayer 4 Players",
+ // Wireless/LAN Play
+ "Local-Single" => "Wireless/LAN Play",
+ "Local-2players" => "Wireless/LAN Play 2 Players",
+ // CC Classes
+ "50cc" => "50cc",
+ "100cc" => "100cc",
+ "150cc" => "150cc",
+ "Mirror" => "Mirror (150cc)",
+ "200cc" => "200cc",
+ // Modes
+ "GrandPrix" => "Grand Prix",
+ "TimeAttack" => "Time Trials",
+ "VS" => "VS Races",
+ "Battle" => "Battle Mode",
+ "RaceStart" => "Selecting a Course",
+ "Race" => "Racing",
+ _ => FormattedValue.ForceReset
+ };
+
+ private static FormattedValue PokemonSVUnionCircle(Value value)
+ => value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
+
+ private static FormattedValue PokemonSVArea(Value value)
+ => value.StringValue switch
+ {
+ // Base Game Locations
+ "a_w01" => "South Area One",
+ "a_w02" => "Mesagoza",
+ "a_w03" => "The Pokemon League",
+ "a_w04" => "South Area Two",
+ "a_w05" => "South Area Four",
+ "a_w06" => "South Area Six",
+ "a_w07" => "South Area Five",
+ "a_w08" => "South Area Three",
+ "a_w09" => "West Area One",
+ "a_w10" => "Asado Desert",
+ "a_w11" => "West Area Two",
+ "a_w12" => "Medali",
+ "a_w13" => "Tagtree Thicket",
+ "a_w14" => "East Area Three",
+ "a_w15" => "Artazon",
+ "a_w16" => "East Area Two",
+ "a_w18" => "Casseroya Lake",
+ "a_w19" => "Glaseado Mountain",
+ "a_w20" => "North Area Three",
+ "a_w21" => "North Area One",
+ "a_w22" => "North Area Two",
+ "a_w23" => "The Great Crater of Paldea",
+ "a_w24" => "South Paldean Sea",
+ "a_w25" => "West Paldean Sea",
+ "a_w26" => "East Paldean Sea",
+ "a_w27" => "Nouth Paldean Sea",
+ //TODO DLC Locations
+ _ => FormattedValue.ForceReset
+ };
+ }
+}