Crafting Items #
In the previous section, we defined the store, crafting, and item catalogs. We also created protos to define the structure of our pets and pet evolutions. In this section, we will author plugins to evolve our pets via Pragma Engine’s crafting system.
Create a Crafting Plugin #
- Create a Kotlin file at
5-ext/ext/src/main/kotlin/PetCraftingPlugin.kt
. - In
PetCraftingPlugin.kt
, define a class that implements theCraftingPlugin
craft()
andmeetsRequirements()
interfaces.
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 PetCraftingPlugin(
val service: Service,
val contentDataNodeService: ContentDataNodeService,
) : CraftingPlugin {
override fun meetsRequirements(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
ext: ExtCraftRequest
): PragmaResult<Unit, List<String>> {
TODO("Not yet implemented")
}
override fun craft(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
inventoryContent: InventoryServiceContent,
requestExt: ExtCraftRequest
): CraftingPlugin.CraftResult {
TODO("Not yet implemented")
}
}
- Replace the
meetsRequirements()
stub with the following code:
Due to known issues with IntelliJ performing type inferences on protos, using a value nested in anext
field can cause false syntax error. Create a local variable with a manually assigned type to hold protoext
fields, as we’ve done withextCraftingRequirements
below.
override fun meetsRequirements(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
ext: 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 (
ext.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:
override fun craft(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
playerInventory: InventoryData,
inventoryContent: InventoryServiceContent,
requestExt: ExtCraftRequest
): CraftingPlugin.CraftResult {
val extCraftingEntry: ExtCraftingEntry = craftingEntry.ext
if (extCraftingEntry.hasPetEvolutionSpec()) {
return petEvolution(
extCraftingEntry.petEvolutionSpec,
destroyedInstancedItems,
requestExt.petEvolutionRequest
)
}
throw PragmaException(PragmaError.UNKNOWN_ERROR, "An unknown error occurred.")
}
- Implement the
petEvolution()
method:
private fun petEvolution(
petEvolutionSpec: PetEvolutionSpec,
destroyedInstancedItems: List<InstancedItem>,
request: PetEvolutionRequest
): CraftingPlugin.CraftResult {
val catalogId = petEvolutionSpec.targetPetCatalogId
val instancedItemGrants = mutableListOf<InstancedItemServerGrant>()
val ext: ExtInstancedItem = destroyedInstancedItems.single().ext
val pet =
ext.pet
.toBuilder()
.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 CraftingPlugin.CraftResult(instancedItemGrants, stackableItemGrants)
}
Create an Instanced Item Plugin #
Create a Kotlin file at
5-ext/ext/src/main/kotlin/DemoInstancedItemPlugin.kt
.Define a class that implements the
InstancedItemPlugin
newInstanced
interface.
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.Pet
import pragma.services.Service
import pragma.utils.Bound
import pragma.utils.RandomProxy
class DemoInstancedItemPlugin(
val service: Service,
val contentDataNodeService: ContentDataNodeService
) : InstancedItemPlugin {
private val randomProxy = RandomProxy()
private val petFactory = PetFactory(randomProxy)
override fun newInstanced(
instancedSpec: InventoryContent.InstancedSpec,
inventoryContent: InventoryServiceContent,
startingInventory: InventoryData,
pendingInventory: InventoryData,
clientRequestExt: ExtPurchaseRequest?,
serverRequestExt: ExtInstancedItemServerGrant?
): InstancedItemPlugin.InstancedItemPluginResult {
TODO("Not yet implemented")
}
override 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()
}
}
Note the presence of a second class PetFactory in this file. Also note that we will not be using the update()
method in this example.
- Implement the
newInstanced()
method:
override 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.hasPetSpec()) {
builder.pet = petFactory.create(instancedSpecExt)
}
if (serverRequestExt != null) {
if (serverRequestExt.hasPetEvolution()) {
builder.pet = serverRequestExt.petEvolution.pet
}
}
return InstancedItemPlugin.InstancedItemPluginResult(builder.build())
}
newInstanced()
takes the ExtInstancedItemServerGrant
is used when the instanced items granted from the crafting request provide the data that populates their ExtInstancedItem
and persists with them in the database.
As this plugin has both the Service
and the ContentDataNodeService
, it should be able to make internal RPC requests and reference other content known to Pragma Engine.
Craft your items #
- Register your plugins by editing
local-dev.yml
.
game:
pluginConfigs:
InventoryService.craftingPlugin:
class: "pragma.inventory.PetCraftingPlugin"
InventoryService.instancedItemPlugin:
class: "pragma.inventory.DemoInstancedItemPlugin"
- Run
make ext
using a terminal from theplatform
directory. - 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
.
- Open Postman, then send
PragmaDev ➨ Public ➨ GetInQueuev1
to enter the login queue. - Send
PragmaDev ➨ Public ➨ Player - AuthenticateOrCreateV2
to log into Pragma Engine as a player. - Grant a pet
fireDragon
and the necessary crafting materials by runningPragmaDev ➨ Game ➨ RPC - Player ➨ Inventory ➨ storePurchaseV4
with the following body:
{
"requestId": 1,
"type": "InventoryRpc.StorePurchaseV4Request",
"payload": {
"data" : {
"ext": {},
"storeId": "materialShop",
"storeEntryId": "craftingKit",
"amount": 1
}
}
}
The player should receive a fireDragon
, 2000 gold
, 2000 fireShards
, and 2 fireGems
. This was specified in Stores.json
.
The fireDragon
should have one of the following attributes with a value between 5 to 10: XP
, Gold
, or Luck
. This was specified in InstancedSpecs.json
.
- Evolve your fireDragon by running
PragmaDev ➨ Game ➨ RPC - Player ➨ Inventory ➨ Crafting Scenarios ➨ craftV1
with the following body:
{
"requestId": 1,
"type": "InventoryRpc.CraftV1Request",
"payload": {
"craftRequest": {
"ext": {
"petEvolutionRequest": {
"craftingLocation": "fireLake"
}
},
"craftingEntryId": "fireDragon2Evolution",
"itemsToDestroy": [
"{{craftableItemId}}"
]
}
}
}
- Confirm that the previously granted
gold
,fireShards
,fireGems
, andfireDragon
were removed from the player inventory. They should have been exchanged for afireDragon2
with the same bonus type as thefireDragon
increased by 2.
Appendix #
The completed PetCraftingPlugin.kt
and DemoInstancedItemPlugin.kt
files are below.