Customizing the Crafting Plugin #
In this section, we’ll go over writing a Crafting Plugin for evolving pets and an Instanced Item Plugin that builds pets with custom ext
data.
Prerequisites:
- The previous Crafting tutorial section for Setting Up the Item Catalog must be completed.
Write the Crafting Plugin #
- Go to
5-ext/ext/src/main/kotlin
. - Create a Kotlin file called
PetTutorialCraftingPlugin.kt
. - Copy the following code into the
PetTutorialCraftingPlugin.kt
file to create a class that implements theCraftingPlugin
interface
Even if you only usecraft()
and notmeetsRequirements()
, both functions must still be implemented in order for the plugin to run.
package pragma.inventory
import pragma.PragmaError
import pragma.PragmaException
import pragma.PragmaResult
import pragma.PragmaResultError
import pragma.PragmaResultResponse
import pragma.content.ContentDataNodeService
import pragma.inventory.ext.*
import pragma.services.Service
class PetTutorialCraftingPlugin(
val service: Service,
val contentDataNodeService: ContentDataNodeService,
) : CraftingPlugin {
override suspend fun meetsRequirements(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
requestExt: ExtCraftRequest
): PragmaResult<Unit, List<String>> {
TODO("Not yet implemented")
}
override suspend fun craft(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
inventoryContent: InventoryServiceContent,
requestExt: ExtCraftRequest
): InventoryModifications {
TODO("Not yet implemented")
}
}
- Replace the empty
meetsRequirements()
function with the following code:
override suspend fun meetsRequirements(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
requestExt: ExtCraftRequest
): PragmaResult<Unit, List<String>> {
val extCraftingRequirements: ExtPurchaseRequirements = craftingEntry.requirements.ext
val errors = mutableListOf<String>()
try {
val itemToDestroy = destroyedInstancedItems.single()
if (
itemToDestroy.catalogId !=
extCraftingRequirements.petEvolutionRequirements.requiredPetCatalogId
) {
errors.add("Pet Evolution Requirement not met.")
}
} catch (e: RuntimeException) {
errors.add("Incorrect number of Pets were offered for evolution.")
}
if (
requestExt.petEvolutionRequest.craftingLocation !=
extCraftingRequirements.petEvolutionRequirements.requiredPlayerLocation
) {
errors.add("Pet Evolution Location not met.")
}
if (errors.isNotEmpty()) {
return PragmaResultError(errors)
}
return PragmaResultResponse(Unit)
}
- Replace the
craft()
stub with the following code and implement thepetEvolution()
method:
override suspend fun craft(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
inventoryContent: InventoryServiceContent,
requestExt: ExtCraftRequest
): InventoryModifications {
val extCraftingEntry: ExtCraftingEntry = craftingEntry.ext
if (extCraftingEntry.hasPetEvolutionSpec()) {
return petEvolution(
extCraftingEntry.petEvolutionSpec,
destroyedInstancedItems,
requestExt.petEvolutionRequest
)
}
throw PragmaException(PragmaError.UNKNOWN_ERROR, "An unknown error occurred.")
}
private fun petEvolution(
petEvolutionSpec: PetEvolutionSpec,
destroyedInstancedItems: List<InstancedItem>,
request: PetEvolutionRequest
): InventoryModifications {
val catalogId = petEvolutionSpec.targetPetCatalogId
val instancedItemGrants = mutableListOf<InstancedItemServerGrant>()
val ext: ExtInstancedItem = destroyedInstancedItems.single().ext
val pet =
ext.pet
.toBuilder()
.setBonus(ext.pet.bonus)
.setBonusAmount(ext.pet.bonusAmount + petEvolutionSpec.bonusIncrement)
.build()
instancedItemGrants.add(
InstancedItemServerGrant(
ExtInstancedItemServerGrant.newBuilder()
.setPetEvolution(PetEvolution.newBuilder().setPet(pet))
.build(),
catalogId,
mutableListOf(request.craftingLocation)
)
)
val stackableItemGrants = mutableListOf<StackableItemGrant>()
return InventoryModifications(
instancedItemServerGrants = instancedItemGrants,
stackableItemGrants = stackableItemGrants
)
}
Write the Instanced Item Plugin #
Go to
5-ext/ext/src/main/kotlin
.Create a Kotlin file called
PetTutorialInstancedItemPlugin.kt
.Copy the following code into the
PetTutorialInstancedItemPlugin.kt
file to create a class that implements theInstancedItemPlugin
’s interface:
Even thoughnewInstanced()
is the only function utilized,update()
must still be implemented in order for the plugin to run.
package pragma.inventory
import pragma.content.ContentDataNodeService
import pragma.inventory.ext.ExtInstancedItemServerGrant
import pragma.inventory.ext.ExtInstancedItemServerUpdate
import pragma.inventory.ext.ExtInstancedItemUpdate
import pragma.inventory.ext.ExtInstancedSpec
import pragma.inventory.ext.ExtPurchaseRequest
import pragma.inventory.ext.ExtInstancedItem
import pragma.inventory.ext.Pet
import pragma.services.Service
import pragma.utils.Bound
import pragma.utils.RandomProxy
class PetTutorialInstancedItemPlugin(
val service: Service,
val contentDataNodeService: ContentDataNodeService
) : InstancedItemPlugin {
private val randomProxy = RandomProxy()
private val petFactory = PetFactory(randomProxy)
override suspend fun newInstanced(
instancedSpec: InventoryContent.InstancedSpec,
inventoryContent: InventoryServiceContent,
startingInventory: InventoryData,
pendingInventory: InventoryData,
clientRequestExt: ExtPurchaseRequest?,
serverRequestExt: ExtInstancedItemServerGrant?
): InstancedItemPlugin.InstancedItemPluginResult {
val instancedSpecExt: ExtInstancedSpec = instancedSpec.ext
val builder = ExtInstancedItem.newBuilder()
if (instancedSpecExt.dataCase == ExtInstancedSpec.DataCase.PET_SPEC) {
if (serverRequestExt != null && serverRequestExt.hasPetEvolution()) {
builder.pet = serverRequestExt.petEvolution.pet
} else {
builder.pet = petFactory.create(instancedSpecExt)
}
}
return InstancedItemPlugin.InstancedItemPluginResult(builder.build())
}
override suspend fun update(
initialInstancedItem: InstancedItem,
instancedSpec: InventoryContent.InstancedSpec,
updateEntry: InventoryContent.UpdateEntry,
inventoryContent: InventoryServiceContent,
startingInventory: InventoryData,
pendingInventory: InventoryData,
clientRequestExt: ExtInstancedItemUpdate?,
serverRequestExt: ExtInstancedItemServerUpdate?
): InstancedItemPlugin.InstancedItemPluginResult {
TODO("Not used in this example")
}
}
class PetFactory(private val randomProxy: RandomProxy) {
fun create(instancedSpecExt: ExtInstancedSpec): Pet {
val bonusType = instancedSpecExt.petSpec.bonusesList.random()
val minVal = instancedSpecExt.petSpec.bonusMin
val maxVal = instancedSpecExt.petSpec.bonusMax
return Pet.newBuilder()
.setBonus(bonusType)
.setBonusAmount(randomProxy.random(minVal, maxVal, Bound.INCLUSIVE))
.build()
}
}
Configure the plugins #
Plugins must be configured in YAML configuration files before they can be utilized by Pragma Engine.
- Open
5-ext/config/local-dev.yml
. - Register the
PetTutorialCraftingPlugin
andPetTutorialInstancedItemPlugin
by ensuring the game section of the config file matches the following:
game:
pluginConfigs:
InventoryService.craftingPlugin:
class: "pragma.inventory.PetTutorialCraftingPlugin"
InventoryService.instancedItemPlugin:
class: "pragma.inventory.PetTutorialInstancedItemPlugin"
Build plugin changes #
Run the following make command using platform
as the working directory to register plugin changes:
make ext
Test the Plugins #
In this section, we’ll test if the plugin logic builds and evolves pets with custom ext
data by running API calls in Postman.
Start Pragma Engine #
Run Pragma Engine via one of the following methods.
Once the engine has started successfully, it prints the message [main] INFO main - Pragma server startup complete
.
Simulate service calls #
Test the PetTutorialCraftingPlugin
and PetTutorialInstancedItemPlugin
by running the required authentication calls, granting the required stackable materials and a pet fire_dragon
, and running a craft request to evolve a fire_dragon
into a fire_dragon_2
.
- Open Postman.
- Navigate to the two service calls
PragmaDev ➨ Public ➨ Operator - AuthenticateOrCreateV2
andPragmaDev ➨ Public ➨ Player - AuthenticateOrCreateV2
. - Click Send for both service calls and check that the response body for each call has
pragmaTokens
with a filledpragmaGameToken
andpragmaSocialToken
. - Go to
PragmaDev ➨ Game ➨ RPC - Operator ➨ Inventory ➨ ApplyInventoryOperationsOperatorV1
and open the service call’s body. - Insert the following code block to add 2000
gold_coins
andfire_shards
, 2fire_gems
, and 1fire_dragon
.
{
"requestId": 1,
"type": "InventoryRpc.ApplyInventoryOperationsOperatorV1Request",
"payload": {
"playerId": "{{test01PlayerId}}",
"dataOperations": {
"stackableItemGrants": [
{
"catalogId": "gold_coins",
"amount": 2000
},
{
"catalogId": "fire_shard",
"amount": 2000
},
{
"catalogId": "fire_gem",
"amount": 2
}
],
"instancedItemServerGrants": [
{
"ext": {},
"catalogId": "fire_dragon"
}
]
}
}
}
- Click Send to grant the player their items. You can also check the payload to make sure the
fire_dragon
has an attribute ofXP
,Gold
, orLuck
with a value between 5 to 10 as specified inInstancedSpecs.json
. - Go to
PragmaDev ➨ Game ➨ RPC - Player ➨ Inventory ➨ Crafting Scenarios ➨ craftV1
and open the service call’s body. - Insert the following code block to make a
craftRequest
for evolving apet
. Note that theext
field contains thepetEvolutionRequest
as defined ininventoryContentExt.proto
and has thecraftingLocation
set tofire_lake
.
{
"requestId": 1,
"type": "InventoryRpc.CraftV1Request",
"payload": {
"craftRequest": {
"ext": {
"petEvolutionRequest": {
"craftingLocation": "fire_lake"
}
},
"craftingEntryId": "fire_dragon_2_evolution",
"itemsToDestroy": [
"<craftableItemId>"
]
}
}
}
- Insert the
instanceId
of thefire_dragon
into<craftableItemId>
initemsToDestroy
. You can find theinstancedId
of thefire_dragon
by going to the payload from the previousApplyInventoryOperationsOperatorV1
service call. - Click Send to evolve the
fire_dragon
into afire_dragon_2
. Check the service call’s payload to confirm the removal of previously grantedgold_coins
,fire_shards
,fire_gems
, and thatfire_dragon
has been exchanged for afire_dragon_2
with the same bonus type as thefire_dragon
increased by 2.
Appendix #
The completed PetTutorialCraftingPlugin.kt
and DemoInstancedItemPlugin.kt
files are below.