Appearance
Release 31
December 16th, 2024
Appearance
December 16th, 2024
Hotfix release
Release 31.1 is the latest hotfix release for Release 31 and contains a fix for a bug introduced in Release 31.0 that breaks the new client redirect feature.
If you are on Release 31.0, you should upgrade to Release 31.1 before doing a game update!
You can now track your key performance indicators right from the LiveOps dashboard, no setup required. Head to the LiveOps dashboard metrics page to check it out.

Client-only patch releases can now be tagged with a Client Patch Version identifier and the Client Compatibility settings in the Metaplay LiveOps dashboard has been extended to support specifying client patch version requirements per client platform. This is useful for the ability to release non-critical fixes to clients without requiring a server update or forcing a client update, while maintaining the ability to force clients to update to a specific patch version per platform at a later time.

The "LiveOps Events" page now has a visual timeline that you can use to organize and view all your events in. This first rollout introduces the concept of groups, rows and colors that can be used to organize your events. We will be working on more editing and navigation features for the next release and feedback is already welcome!

Metaplay now supports authenticating users by their Steam accounts on server login, see Implementing Steam Authentication for details!
OrderedDictionary -> MetaDictionary: The class OrderedDictionary has been renamed to MetaDictionary in preparation for supporting .NET9. This rename causes no change in functionality and any serialized data continues to be compatible. The reason for this is that .NET9 introduces System.Collections.Generic.OrderedDictionary which technically could co-exist with Metaplay.Core.OrderedDictionary but would necessitate using the namespace-qualified name of the class and would create the potential of accidentally using the system implementation where the Metaplay implementation was intended. See Migration Guide for Self-Service Customers for guidance on migrating your project.
New MetaConfigId Reference Type: We've introduced MetaConfigId for optimizing the server memory footprint of games with large game configs and complicated experiments setups. MetaConfigId, which remains a simple config ID in memory, can be used in place of MetaRef, which resolves the referenced item into memory. For more information, see Using MetaConfigId Instead of MetaRef.
[BigQueryAnalyticsFormat(BigQueryAnalyticsFormatMode.ExtractDictionaryElements)] can be used to format dictionary-like objects as if dictionary elements were object fields.SubscribeToAsync(..., bool allowMultiple = false) to opt-in to allowing multiple subscriptions between two entities.EntityAskAsync(...) calls and [EntityAskHandler] definitions. You can opt in per request-response type pair by deriving them from EntityAskRequest<TResponse> and EntityAskResponse instead of plain MetaMessage.IEntityClientContext now has EarlyUpdate. EarlyUpdate() is called for every entity context before Update() is called.insertAll streaming API, with up to 2 TiB of data ingestion per month available for free.bigquery.tables.updateData permissions.DynamicEnum now supports declaring the enum values dynamically on initialization via a static method on the DynamicEnum derived class identified by DynamicEnumFactoryAttribute.GameConfigBuildUtil.PublishGameConfigArchiveToServerAsync() now also supports setting the name and description.Type.HasCustomAttribute() allows checking an attribute presence without allocation, unlike Type.GetCustomAttribute() != null.MetaplayCoreOptions for tagging the current ClientPatchVersion and the associated logic for refusing unsupported client patch versions on client login based on the current client patch version requirements in ClientCompatibilitySettings.ClientPatchVersionTooOld and the associated client-side MetaplayConnection terminal error state. The default MetaplayClient implementation maps this error into the existing ConnectionLostReason.ClientVersionTooOld which is also used for the case when the client logic version is too old.MP_OD_01 and MP_OD_02 for renaming OrderedDictionary to MetaDictionary.RandomPCG now has an overload for NextLong() with a exclusive max parameter.MetaDuration.Second, MetaDuration.Minute, MetaDuration.Hour and MetaDuration.Day.IAdminApiServices interface.TryReassignPlayer API to the league integration, which allows a player to be reassigned to a new division within the same season.DefaultEnvironmentConfigProvider's various path name properties are now correctly virtual, allowing you to override them in a derived class (which automatically will become the active provider thanks to the integration registry system).EntityAskAsync and SubscribeToAsync, will now throw EntityCrashedError instead of UnexpectedEntityAskError if entity crashes while processing some preceding message.SubscribeToAsync will now throw EntityUnreachableError if target entity cannot be reached.UnsubscribeFromAsync will no longer throw TimeoutError if unsubscription cannot be guaranteed by the time limit and instead returns false.LiveOpsTimelineManager, to reduce load and responsibilities from GlobalStateManager which previously managed them. This is mostly an SDK-internal change and likely does not affect you. If you send messages directly to GlobalStateManager for managing LiveOps Events, you now need to send those messages to LiveOpsTimelineManager instead.PlayerAddMail, PlayerDeleteMail and PlayerForceDeleteMail now use client/server action base classes rather than setting ModelActionExecuteFlags via attribute to make it easier to understand whether they are client or server actions.game_entity_asks_total and game_entity_ask_errors_total metrics have their message label values changed from <MessageType> to Ask-<MessageType> for EntityAsks.logger or adminApi parameters.SharedApiBridge and ServerApiBridge to encapsulate the game-specific helpers from GameAdminApiController to make them available in controllers deriving from other controllers, notably from MetaplayWebhookController. The helper methods are available directly on GameAdminApiController but must be invoked via ApiBridge.HelperMethod() in other controllers.PlayerLoginEvent.SessionLengthApprox. This is expected to reduce SessionLengthApprox by about 20 seconds.MetaplayClient.Connection.Reconnect().EntityImportEntityIdRemapper will now remap EntityIds that are not part of the current import entity set or are not persisted in the database to EntityId.None.stdin stream even if --Environment:EnableKeyboardInput=false is not given.IMetaplayLifecycleDelegate.OnSessionLost() is now called also when connection is closed with MetaplayConnection.Close().Metaplay.Core.Json.JsonSerialization now supports deserializing base classes' properties with private setters. This is needed for building game configs from JSON data, because some SDK-defined base classes have properties with private setters yet expect those to be settable via deserialization.MModal.OverviewListItem.asLink() option to the initialization API to make it possible to inject links into the overview lists.getGameServerHelloSubscriptionOptions() subscription. This provides access to the /hello endpoint of the game server that contains immutable dashboard configuration, like the current environment name and feature flags. The same data is now removed from coreStore.MInputSingleSelectDropdown component for better type safety.Experiment Detail page has been updated and is now organized into three main tabs: Details Tab: Contains the experiment's details defined in the game configs.Audience & Targeting Tab: Contains details on the experiment's audience and targeting criteria.Audit Log Tab: Displays a log of all changes made to the experiment.Experiment Detail page overview card has been updated and details of the experiment's target audience have been moved to the Audience & Targeting tab.coreStore.hello has been removed and replaced by the new useStaticInfos() composable in @metaplay/gameServerApi. This composable provides access to the same underlying data, but in a more consistent and easier-to-use way.coreStore.getEnvironmentFamily() has been removed as it was a simple getter for helloData.environmentFamily. Use the new useStaticInfos() composable instead.replace option has been removed from the addUiComponent() API's layout options as it was ambiguous and rarely used. Call removeUiComponent() followed by addUiComponent() to achieve the same effect.okButtonDisabled prop for disabling the OK button in modal components (MActionModal, MActionModalButton) has been removed. To disable the OK button, you must now provide a message to the new okButtonDisabledTooltip prop explaining why it’s disabled. This message will be displayed as a tooltip when users hover over the button.ConfigContentCard experiment search functionality now allows searching by both the experiment name and experiment ID.View Audit Log Event page.DashboardHeaderColorInHex colors.Settings View has been updated and is now organized into two main tabs: Common Settings Tab: Contains frequently used system settings.Advanced Tab: Contains advanced controls to help you manage your system.MInputNumber number component did not work.MTabLayout component where the dropdown menu was hidden in mobile view.MTabLayout where the correct tab would not be selected when navigating backwards to a previously viewed page.index value on item-card slot of MetaListCard, which was incorrect.MInputSingleFile* components where they could have a value that wasn't visible in the UI.LeagueSeasonRankCard that caused it to only access divisions from the lowest rank.Metaplay SDK has deprecated support for Unity 2021.3 as it has reached end-of-life and is no longer supported by Unity. The lowest supported Unity version is now 2022.3. While this release still works with Unity 2021.3, we plan to remove support for it in R32, tentatively scheduled for March 2025.
Still using 2021.3?
If you cannot reasonably upgrade to 2022.3 or later in the near future, please let us know.
Please apply the following changes to your project to ensure compatibility with the latest Metaplay SDK.
Backward-Incompatible Changes
Bump your game's MetaplayCoreOptions.supportedLogicVersions to force a synchronized update of your game client and server.
Helm chart v0.7.0 is recommended for cloud deployments and minimum required version is now v0.6.3.
Migration Steps:
Update your CI pipelines for all your environments to use the latest version of the Helm chart.
When using GitHub Action scripts, make the following change:
jobs:
...
build-and-deploy-server:
...
with:
- helm-chart-version: "0.x.y"
+ helm-chart-version: "0.7.0"With other CI systems, apply the following change (or the equivalent in your CI system):
- export HELM_CHART_VERSION="0.x.y"
+ export HELM_CHART_VERSION="0.7.0"Roll out the change to each environment by running the CI job to build and deploy a new version. You can do this for each environment separately, whenever is a good time for you.
Premium SDK Update Support
If your support contract includes Metaplay-provided SDK updates, all the following steps have already been applied to your project. You can skip this migration guide!
This guide offers step-by-step instructions for migrating your project to the latest version of the Metaplay SDK. You can skip the migrations steps for features you are not using in your project.
The following core SDK changes affect all Metaplay projects:
Metaplay's built-in database schema has changed, and you need to apply the migration steps to your project.
Migration Steps:
Generate the database schema migration code with:
# Install or update the EFCore tool:
Backend/Server$ dotnet tool install -g dotnet-ef
# Then, generate the migration code:
Backend/Server$ dotnet ef migrations add MetaplayRelease31Then, add the generated files to your project's source control. For example, using Git:
Backend/Server$ git add .
Backend/Server$ git commit -m "Database schema migrations"The migration steps will be automatically applied when you deploy the updated game server into an environment.
The following LiveOps Dashboard changes affect projects that have a game-specific dashboard project:
You should ensure that you have Node version 22.11.0 (the latest 22.x version at the time of writing) installed. To check the current version, run node --version. If you are using nvm, you can update Node with:
# Install Node 22.11.0 with Node Version Manager (nvm).
nvm install 22.11.0
# Use the new version.
nvm use 22.11.0Update the Node version in your project's package.json file to reflect the new requirement:
"engines": {
"node": "20.x"
"node": ">=20"
}Migration Steps:
git clean -fdx ':(glob)**/node_modules/*' from the root of your repository. This clears any currently installed dependencies.git clean -fdx ':(glob)**/dist/*' from the root of your repository. This clears any previously built files.pnpm-lock.yaml file from the root of your repository. This clears the cached dependency versions.As usual, we have updated the underlying dependencies and configurations of the LiveOps Dashboard. This causes changes to several configuration files, which you will need to update in your dashboard project. We use the MetaplaySDK/Frontend/DefaultDashboard folder as the source of truth for these files.
Migration Steps:
Update the package.json to update your project's dependencies.
package.json file directly from the MetaplaySDK/Frontend/DefaultDashboard directory. Next, restore the name property inside the file to that of your project. This is typically of the form "name": "<projectName>-dashboard".Copy and overwrite the following files:
vite.config.tsvue-compat.d.tsindex.htmlUpdate the compilerOptions in your tsconfig.json file to match the configuration in MetaplaySDK/Frontend/DefaultDashboard. Remove the target, module, and moduleResolution options as they are no longer required.
{
"compilerOptions": {
"composite": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler"
}
}Run pnpm install in your 'Dashboard' folder. This recreates all of the above files and folders with the correct dependencies.
As a part of continued code review and polish of the dashboard codebase, we have done a few breaking changes to internal APIs that you may be using in your custom components.
coreStore.hello to the useStaticInfos() ComposableOur /hello endpoint is the first thing that the dashboard fetches to access its initial configuration, such as feature flags. We have reorganised and refactored how this data is fetched and stored in the dashboard to make it more consistent and easier to use.
You need to update any possible call sites from coreStore.hello to the new useStaticInfos() composable.
Migration Steps:
Import and access the useStaticInfos() composable.
import { useStaticInfos } from '@metaplay/gameServerApi'
const staticInfos = useStaticInfos()Update coreStore.hello callsites to use the new composable.
const environmentName = coreStore.hello.environment
const environmentName = staticInfos.environmentInfo.environmentName Remove references to the now unnecessary coreStore.
import { useCoreStore } from '@metaplay/core'
const coreStore = useCoreStore() Here is the updated schema for the data returned by useStaticInfos() for quick reference:
type StaticInfos = {
projectInfo: {
projectName: string;
};
environmentInfo: {
environmentName: string;
environmentFamily: "Local" | "Development" | "Staging" | "Production";
isProductionEnvironment: boolean;
grafanaUri: string | null;
kubernetesNamespace: string | null;
};
gameServerInfo: {
buildNumber: string;
commitId: string;
};
liveOpsDashboardInfo: {
playerDeletionDefaultDelay: string;
authConfig: {
type: string;
allowAssumeRoles: boolean;
rolePrefix: string | null;
logoutUri: string | null;
};
};
featureFlags: {
pushNotifications: boolean;
guilds: boolean;
asyncMatchmaker: boolean;
web3: boolean;
playerLeagues: boolean;
localization: boolean;
liveOpsEvents: boolean;
gameTimeSkip: boolean;
googlePlayInAppPurchaseRefunds: boolean;
removeIapSubscriptions: boolean;
};
}coreStore.getEnvironmentFamily() to useStaticInfos()This function was a simple getter for coreStore.hello.environmentFamily and has been removed in favour of directly accessing the data via the new useStaticInfos() composable that now has the same info at environmentInfo.environmentFamily.
Migration Steps:
Import and access the useStaticInfos() composable.
import { useStaticInfos } from '@metaplay/gameServerApi'
const staticInfos = useStaticInfos()Replace coreStore.getEnvironmentFamily() with staticInfos.environmentInfo.environmentFamily.
const environmentFamily = coreStore.getEnvironmentFamily()
const environmentFamily = staticInfos.environmentInfo.environmentFamily or
import { getEnvironmentFamily() EnvironmentFamily } from '@metaplay/core'
import { useStaticInfos } from '@metaplay/gameServerApi'
getEnvironmentFamily() === EnvironmentFamily.Local
staticInfos.environmentInfo.environmentFamily === 'Local'Remove references to the now unnecessary coreStore.
import { useCoreStore } from '@metaplay/core'
const coreStore = useCoreStore() addUiComponent() Calls That Use the replace Optionreplace has been removed from the addUiComponent() API's layout options as it was ambiguous and rarely used. Call removeUiComponent() followed by addUiComponent() to achieve the same effect.
Migration Steps:
Call removeUiComponent() first and remove the replace option from the addUiComponent() call.
initializationApi.removeUiComponent('OverviewView', 'GlobalIncidents')
initializationApi.addUiComponent(
'OverviewView',
{ uniqueId: 'CustomGlobalIncidents', vueComponent: async () => await import('./MyOwnIncidentsCard.vue') },
{ position: 'replace', targetId: 'GlobalIncidents' }
)For custom components targeting the Settings View, update your code to ensure they are assigned to either the System/Common or System/Advanced tab.
Migration Steps:
Replace any references to System/Details with one of the following:
initializationApi.addUiComponent('System/Details',
initializationApi.addUiComponent('System/Common',
// Or
initializationApi.addUiComponent('System/Advanced', The okButtonDisabled prop for disabling the OK button in modal components (MActionModal, MActionModalButton) has been replaced by okButtonDisabledTooltip prop.
Migration Steps:
okButtonDisabled with okButtonDisabledTooltip in your dashboard project.MActionModal(
- :ok-button-disabled
+ :ok-button-disabled-tooltip="Reason why this button is disabled"
)MActionModalButton(
- :ok-button-disabled
+ :ok-button-disabled-tooltip="Reason why this button is disabled"
)MActionModalButton(
- :ok-button-disabled="!customForm.valid"
+ :ok-button-disabled-tooltip="!customForm.valid ? 'Reason why this button is disabled' : undefined"
)If you have customized the role-to-permission table in your Options.*.yaml files, you need to add two additional permissions to the table.
The following new dashboard admin permissions have been introduced:
api.players.update_logic_version - Controls who can force update an individual player's logic version.api.metrics.view - Controls who can view metrics data.Migration Steps:
Specify which roles should have access to these permissions in your Backend/Server/Config/Options.*.yaml (whichever file you use to configure the access). You can modify the roles as needed.
AdminApi:
Permissions:
...
api.players.update_logic_version: [ game-admin, customer-support-senior, customer-support-agent ]
api.metrics.view: [ game-admin ] The following migrations apply to games that have implemented custom ASP.NET controllers. These are generally classes inheriting GameAdminApiController.
The Metaplay base controller class constructors no longer need to be passed any arguments.
Migration Steps:
Remove the now-unnecessary constructor arguments and call to base class constructor:
-public MyController(ILogger<MyController> logger, IActorRef adminApi) : base(logger, adminApi)
+public MyController()Optionally, you can now completely remove any fully empty controller constructors:
-public MyController()
-{
-}The following changes to Prometheus Metrics may affect your alerting and monitoring stacks:
message Label Formatgame_entity_asks_total and game_entity_ask_errors_total message label format has changed to clarify the difference of EntityAsks and EntitySubscribes. Asks now produce labels message=Ask-<MessageType> instead of message=<MessageType>. Subscribes continue to produce message=Subscribe-<TargetEntityKind>-<MessageType>. If your metric processing depends on the exact message labels, you need to update your parsing logic.
Migration steps:
game_entity_asks_total{message="FooMessage"}, convert the query to game_entity_asks_total{message="Ask-FooMessage"}.game_entity_ask_errors_total{message="FooMessage"}, convert the query to game_entity_ask_errors_total{message="Ask-FooMessage"}.The following changes require integration updates only if your project uses a custom MetaplayClient implementation and therefore handles client connection error states manually.
TerminalError.ClientPatchVersionTooOldFor information on client patch versions see Client Patches.
When clients of unsupported patch versions attempt to connect to the server the connection attempt will result in an error state of TerminalError.ClientPatchVersionTooOld. This should be handled by the game by directing the player to update to the latest released version, similarly to how the error state of TerminalError.LogicVersionMismatch is handled when the advertised supported logic version is greater than the client's.
Migration steps:
TerminalError.ClientPatchVersionTooOld. Use the existing implementation for handling TerminalError.LogicVersionMismatch.These changes affect you in case you happen to use any of the APIs changed. You can build your project to get a list of any incompatibilities instead of going through the list one item at a time.
AnalyticsLabel NamespaceThe AnalyticsLabel type has been moved from the server-only assembly and Metaplay.Cloud.Analytics namespace to the shared assembly and Metaplay.Core.Analytics namespace.
Migration Steps:
AnalyticsLabel not being found, add statement using Metaplay.Core.Analytics;.PlayerClientContext.Update() CallManually calling PlayerClientContext.Update() is deprecated and is handled automatically in ClientStore.UpdateLogic.
Migration Steps:
PlayerClientContext.Update() call being obsolete, remove the call.OrderedDictionary to MetaDictionaryTo eliminate the name conflict with System.Collections.Generic.OrderedDictionary introduced in .NET 9 the class Metaplay.Core.OrderedDictionary has been renamed to Metaplay.Core.MetaDictionary. For SDK update convenience, the SDK in R31 still contains a migration assistance implementation of Metaplay.Core.OrderedDictionary. This means that your existing code using OrderedDictionary should continue to compile and work, but we strongly recommend updating all uses of it to use MetaDictionary instead, in preparation for updating to .NET 9. The migration assistance helper class OrderedDictionary is marked as Obsolete and any references to the class will yield compiler warnings that you can use to verify that all references have been update.
Note that this change is reflected in the signature of various API entrypoints in the MetaplaySDK code, and this will cause build errors for any integration code that expects the old signature using OrderedDictionary parameters.
Migration Steps:
Rename all occurrences of OrderedDictionary to MetaDictionary both in your server and client code.
Update any uses of ToOrderedDictionary() to use ToMetaDictionary() instead.
To aid in the renaming, the Metaplay SDK contains a Roslyn code analyzer and an associated code fix that can be used for conveniently carrying out the renaming in your code. The relevant code analyzer diagnostics are MP_OD_01 (use of the OrderedDictionary<,> type) and MP_OD_02 (invocations of the ToOrderedDictionary() method).
Visual Studio 2022 supports batch fixing of analyzer issues via navigating to an occurrence of the issue and clicking on "Fix all occurrences".
The codefixes can also be applied from command line using the dotnet format tool, the invocation is:
dotnet format <workspace> analyzers --diagnostics MP_OD_01 MP_OD_02The batch execution of code fixes happens in a context of a workspace (solution or project). To carry out the renaming both in server and (Unity) client code you will need to run the operation on both your server solution file and the generated Unity solution file.
RandomPCG.NextLong() OverloadA new overload of RandomPCG.NextLong(long maxExclusive) with an exclusive max parameter was added in this release. If you have modified the SDK or implemented a custom conflicting overload, check the compatibility of those methods.
Migration Steps:
BigQuery analytics sink now support custom Analytics Context classes, and all custom analytics class types are checked at server launch time to be BigQuery-compatible. The check is performed regardless if BigQuery sink is enabled or not.
Migration Steps:
Unknown analytics event field format in <SomeContextType>, either change the type of the field to become formattable or ignore the field with [BigQueryAnalyticsFormat(BigQueryAnalyticsFormatMode.Ignore)].Released on: January 13th, 2025
ClientHello.SupportedLogicVersionsLegacy for client compatibility with server deployments using MetaplaySDK before R31. This is necessary for the new client redirection functionality to work, as ClientHello needs to be deserialized before the redirection logic.We are aware of the following issues in this release:
When using Unity Editor 2021, each time the domain is reloaded the following messages are printed in Editor logs. These messages can be ignored.
<<<Error>>> Could not find method 'MenuItemExists', Unity API likely changed. This is a bug, please report this to Metaplay.
<<<Error>>> Could not find method 'RebuildAllMenus', Unity API likely changed. This is a bug, please report this to Metaplay.MetaplaySDK.OnApplicationAboutToBePaused() Has No EffectMetaplaySDK.OnApplicationAboutToBePaused() is expected to extend session lifetime for the duration of the application pause. Due to a logic error, the session lifetime is not extended.
This bug will be fixed in Metaplay Release 32.
Clients build with SDK version R31.0 will not be able to connect to server builds with SDK version before R31. As the R31 SDK update is a game protocol compatibility breaking update, an actual game session would not be able to start with the older server in any case. The "new client redirect" feature, however, requires the old server to be able to read the initial ClientHello message sent by the new client.