Grant, update, and remove data from trusted backends #
When defining a player data operation you control the access through the @PlayerDataOperation annotation.
The three trusted backend types are: SessionType.PARTNER, SessionType.OPERATOR, SessionType.SERVICE.
Example Operation #
Below is an example of operation that grants a gear item. This can be called from the game-server, the game operator portal, or another game service or plugin.
package documentation
import pragma.playerdata.Component
import pragma.playerdata.Context
import pragma.playerdata.Entity
import pragma.playerdata.FieldNumber
import pragma.playerdata.PlayerDataContentLibrary
import pragma.playerdata.PlayerDataOperation
import pragma.playerdata.PlayerDataRequest
import pragma.playerdata.PlayerDataResponse
import pragma.playerdata.PlayerDataSnapshot
import pragma.playerdata.PlayerDataSubService
import pragma.rpcs.SessionType
import pragma.types.SerializedName
import pragma.utils.ComponentId
import pragma.utils.EntityId
data class GrantGear(
val gearId: String,
val skinId: String,
val minDamage: Int,
val maxDamage: Int
) : PlayerDataRequest
data class GrantGearResponse(
val gearEntityId: EntityId,
val gearComponentId: ComponentId,
) : PlayerDataResponse
@Suppress("unused")
class LoadoutSubService(
contentLibrary: PlayerDataContentLibrary
) : PlayerDataSubService(contentLibrary) {
@PlayerDataOperation(sessionTypes = [SessionType.PARTNER, SessionType.OPERATOR, SessionType.SERVICE])
fun grantGear(request: GrantGear, context: Context): GrantGearResponse {
/**
* Note: This is not a complete example.
* Gear ids should be verified against a content catalog.
* See the docs on Content or the Player Data Tutorial for example use of content.
*/
val entity = context.snapshot.getGearInventory()
/**
* only store properties that are unique
* global properties should be defined and stored in the content catalog
*/
val componentId = entity.addComponent(
Gear(
request.gearId,
request.minDamage,
request.maxDamage,
request.skinId
)
)
return GrantGearResponse(entity.instancedId, componentId)
}
private fun PlayerDataSnapshot.getGearInventory(): Entity {
return getOrCreateUniqueEntity("GearInventory")
}
}
@SerializedName("1759966426")
data class Loadout(
@FieldNumber(1) var equippedGear: MutableList<ComponentId>,
@FieldNumber(2) var heroSelected: String,
) : Component
@SerializedName("1759966432")
data class Gear(
@FieldNumber(1) var gearId: String,
@FieldNumber(2) var minDamage: Int,
@FieldNumber(3) var maxDamage: Int,
@FieldNumber(4) var skinId: String,
) : Component
Call from Unreal Partner SDK #
void AUnicornGameMode::GrantGear(
const FString &PlayerId, // players game id
const FGearToGrant &GearToGrant
) {
// providing type - assign as private property on the GameMode etc ...
const Pragma::FServerPtr PartnerSdk;
PartnerSdk->PlayerDataService().Loadout().GrantGear(
PlayerId,
GearToGrant.GearId,
GearToGrant.SkinId,
FMath::RandRange(1, 10),
FMath::RandRange(11, 15),
FOnLoadoutGrantGearDelegate::CreateLambda(
[](const TPragmaResult<FPragma_PlayerData_GrantGearResponseProto>
&Result) {
if (Result.IsSuccessful()) {
// hand off ids to client as needed ...
const auto [GearEntityId, GearComponentId] =
Result.Payload<FPragma_PlayerData_GrantGearResponseProto>();
} else {
// check and handle failures
}
}));
}
Call from services and plugins #
When the operation allows the SessionType.SERVICE, you can call this operation from another game service or game plugin.
Use the PlayerDataClient to use the Kotlin interfaces vs the proto interfaces.
package demo.gameinstance
import documentation.GrantGear
import documentation.GrantGearResponse
import pragma.content.ContentDataNodeService
import pragma.gameinstance.ExtEndGameRequest
import pragma.playerdata.kotlinclient.PlayerDataClient
import pragma.gameinstance.ExtPlayerGameResult
import pragma.gameinstance.GameInstance
import pragma.gameinstance.GameInstancePlugin
import pragma.gameinstance.PlayerGameResult
import pragma.logging.PragmaLoggerFactory
import pragma.services.Service
class DemoGameInstancePlugin(
val service: Service,
val contentDataNodeService: ContentDataNodeService,
// Use a PlayerDataClient when making service calls to Player Data
private val playerDataClient: PlayerDataClient = PlayerDataClient(service),
) : GameInstancePlugin {
val logger = PragmaLoggerFactory.getLogger(DemoGameInstancePlugin::class)
override suspend fun handleBackendEndRequest(
gameInstanceSnapshot: GameInstance.GameInstance,
playerGameResults: List<PlayerGameResult>,
requestExt: ExtEndGameRequest,
) {
// example calling grant gear for each player
playerGameResults.forEach { gameResult ->
// define ExtPlayerGameResult with custom data sent from game server
val ext: ExtPlayerGameResult = gameResult.ext
val grantGearRequest = GrantGear(
ext.newGear.gearId,
ext.newGear.skinId,
(1..10).random(),
(11..15).random()
)
// use the client to call an operation
val result = playerDataClient.doOperation(
grantGearRequest,
gameResult.playerId
)
result.fold(
{ response -> response as GrantGearResponse },
{ failure ->
when {
failure.isApplicationError -> {/* expected failures */}
else -> { /* critical ServiceError */}
}
}
)
}
// ...
}
}
Call from the Game Operator Portal #
You can call operations from the game portal. Navigate to a player’s support page, select the player data tab, select the wanted operation from the Operations drop down.
