Content Data #
Studios can use the Content Data system to create and interact with game content managed by the platform. This includes inventory, player loadouts, and player progression artifacts like missions, quests, and battlepasses.
Understanding content data #
Pragma Engine’s Content Data system is designed for structured, versioned content management. It includes versioned content packages that can be quickly promoted to multiple environments, ensuring production is set up identically to staging. Content Data works together with the Player Data and Inventory services to support live service game content with a comprehensive set of tools and workflows.
Live service games need to patch content constantly, requiring tools and patterns around data versioning and data migration. These must be coordinated across platforms and across multiple game client versions.
The Content Data system enables live update scenarios such as rebalancing weapons without game client or server patches. It also supports more complex schema migration scenarios in which new fields are added, removed, and renamed.
Content Data system overview.
In the sections below, we’ll take a look at how the pieces of the Content Data system fit together, and how to create, access, and leverage new content.
Provided vs Custom Content #
The Content Data system supports two categories of content definitions: provided and custom.
Provided content:
This is content used by the Pragma Engine Inventory service, such as instanced and stackable item specs. These can be customized by editing ext
fields in the 5-ext
folder.
Custom content: This content is completely built by the user, who can define custom services, custom JSON, and custom proto definitions.
Commands #
type | explanation | command |
---|---|---|
init | - initializes a new type of content by creating empty JSON and metadata files - related proto and Content Handler must be defined | java -jar 5-ext/target/$(COMPANY).jar contentdata init -d <CONTENT PATH> -c <CONTENT HANDLER CLASS NAME> <CONTENT TYPE> - CONTENT PATH : path to the content directory, with a default of 5-ext/content - CONTENT HANDLER CLASS NAME : class name of the content handler- CONTENT TYPE : name of content file without “.json” extension |
plan | - prints a summary of changes that can be applied | java -jar 5-ext/target/$(COMPANY).jar contentdata plan -d <CONTENT PATH> - CONTENT PATH : path to the content directory, with a default of 5-ext/content |
apply | - applies and packages content changes made to the src files- prints a plan summary of changes.Response options: -y for auto-approve-v for verbose (prints out entire JSON objects) | java -jar 5-ext/target/$(COMPANY).jar contentdata apply -d <CONTENT PATH> - CONTENT PATH : path to the content directory, with a default of 5-ext/content |
Adding content #
File Architecture #
Two types of files make up the basics of adding and updating content in the Content Data system: protobuf files that define blueprints for content, and JSON files that populate these blueprints with specific content.
Content Protos #
Content protos are the source of truth for content definitions. They provide a blueprint for possible fields and attributes for each type of content.
JSON Files #
These files populate the actual instances of protos with user-defined data. They must perfectly match proto definitions.
Adding content with protos and JSON files.
Steps for Making New Content #
The content addition process follows several steps, which we’ll cover in this section:
- Creating the protos, which serve as blueprints for the JSON files
- Adding content with the JSON files
- Defining Content Handlers
- Running the apply command to verify and generate a JSON package that services can read
Creating the protos #
Edit or create any relevant protos in the 5-ext/ext-protos
folder. If you’re editing provided content files, this is where you put the customizable ext fields.
Be sure to only edit files in5-ext
! If you edit files in the1-protos
folder, it won’t work.
Creating the JSON files #
Create and populate the two required JSON src/
files with the desired content. You can use the init
command (in bash) to generate these files, or you can create them manually.
The two required files:
- a
<content-type>.json
file that contains the list of content objects using the blueprints from the protos and defining specific fields. - a
<content-type>.metadata
file that contains metadata information, such as a link to the Content Handler and a version incrementer.
Remember that JSON objects can represent nested messages, so make sure that they have all fields filled out, not just ext
fields.
Defining Content Handlers #
Content Handlers are a type of plugin assigned within the metadata file. ContentHandler
is the class responsible for validating, containing, and providing access to content protos.
- Provided Inventory service content comes with default Content Handlers.
- Custom content must have an associated custom-defined Content Handler.
Custom-defined Content Handlers should be created and populated in the relevant service directory within 5-ext/ext/src/main/kotlin
.
Defining a custom Content Handler for provided content is possible to enable certain features, such as cross-content validation across ext
fields, which is covered below in Cross-Content Validation.
-> Creating a custom Content Handler
At minimum, Content Handlers must contain the class along with the following information:
the handler’s name and the fact that it’s a
ContentHandler
, along with the custom proto’s location, name, and its class:class [handler name]: ContentHandler<[package].[proto name]>([package].[proto name]::class)
the
idFromProto
override that returns the proto IDoverride fun idFromProto(proto: [package].[proto name]): String { return proto.[id field] }
Once the Content Handler has been created, you must assign it in the metadata file nested under applyTriggers:
"contentHandler": "[Content Handler class name]"
Applying changes and validating #
Run the apply
command (in bash or RPC for dev-only) to verify data and generate a JSON package/
file that services can now read.
Do not make the package/
file yourself, as this would skip the validation step. We also recommend never editing package files manually.
-> Basic Validation
It’s easy to have typos or missing data, especially when working with nested protos. If you update your source of truth directly, you can potentially crash your game.
The apply
command makes sure that when new content is applied, it’s been validated. It checks that the JSON matches the proto schema directly. Invalid content will prevent changes from being applied, and will offer specific error details to give users the chance to fix content immediately.
In addition to the validation of proto schemas, you can write custom validation for protos, for example, if you’d like to verify that a specific field is never empty.
-> Cross-Content Validation
Cross-content validation is also available.
For provided content, Pragma Engine validates content across nested IDs (outside of ext
) during the contentdata apply
command. Validation of custom content (across nested ext
s) can be added to a Content Handler under the validateExtWithAllContent
function.
To validate acrossext
fields in provided content, a new Content Handler must be written. The new Content Handler needs to inherit from the provided Content Handler to maintain non-ext
content validation.
The validateExtWithAllContent
function must contain these steps:
- Pull out relevant
contentData
from thecontentByType
map. The keys are strings of content types, so use the content type’s file name without the JSON extension.InventoryCrossContentValidator
can be used to make this step easier when validating across provided Inventory content.
- Pull out nested content IDs from the exts.
- These must be stored in a
mutableSetOf<CorrespondingIds>()
.CorrespondingIds
is a class that holds theidToValidate
and theparentId
. This ensures error messages are clear to assist with debugging.
- These must be stored in a
- Check that nested IDs are valid.
- Confirm that IDs are all part of the relevant content data type using
ContentData.validateIds(idsToValidate: Set<CorrespondingIds>, contentType: String)
.
- Confirm that IDs are all part of the relevant content data type using
Accessing and leveraging content #
To use the content created in the previous section, it must be accessed by the ContentDataNodeService
. Once this is set up, plugins can use the content.
Giving plugins access to your new content.
Enable content access #
Content access is built on two main pieces:
- the
ContentDataNodeService
, which calls - the
ContentData
interface
The ContentDataNodeService
is a service used to fetch specific content. This uses the following call, and requires the content proto and the .json
file resource path:
fun <ContentProto: GeneratedMessageV3> getHandler(
resourcePath: String //<content-type>.json
): ContentData<ContentProto> {
The getHandler
returns a ContentData
interface, which provides read-only access to content through a map where keys are the content ID. The getHandler
only returns the requested content type.
This interface has two primary functions:
get(id: String)
takes in the string ID and provides the content proto.contains(id: String)
is a helper function that provides a boolean confirming if content is available.
Leverage content with plugins #
Plugins can access all content visible to the ContentDataNodeService
.
Provided plugins are already set up to pull in the ContentDataNodeService
. Custom plugins that want to use content will need the following line added to their run()
function:
content.init(nodeServicesContainer[ContentDataNodeService::class])