Building the Cooking Plugins #

In this section, we’ll build the plugins for cooking.


  1. Navigate to platform/5-ext/ext/src/main/kotlin/ in IntelliJ’s Project view.
  2. (if necessary) If the icon for the kotlin folder isn’t blue, right click it and select Mark directory as ➨ Sources root.
  3. Right click the kotlin folder and chose New ➨ Package. Name it cooking.
  4. In the cooking folder, choose New ➨ Kotlin class/file and name it CookingCrafter.kt. Replace the contents with the following code:
Due to known issues with IntelliJ performing type inferences on protos, using a value nested in an ext field can cause false syntax error. Create a local variable with a manually assigned type to hold proto ext fields, as we’ve done with incompleteFoodExt below.
Method overview
  • meetsRequirements — called by Pragma Engine to check that the craft request is valid:
    • checks that the player has the required items in their inventory
    • checks that the incomplete food items can be redeemed
  • startCooking — called for the first step of cooking to cook ingredients into incomplete food
  • completeCooking — redeems incomplete food for the completed version
  1. Create another kotlin file in this directory named DemoCraftingPlugin.kt. Replace the contents with the following code:
Due to known issues with IntelliJ performing type inferences on protos, using a value nested in an ext field can cause false syntax error. Create a local variable with a manually assigned type to hold proto ext fields, as we’ve done with extCraftingRequirements below.
package cooking

import pragma.inventory.*
import pragma.inventory.ext.CookingCompleteRequirements
import pragma.inventory.ext.CookingCompleteSpec
import pragma.inventory.ext.CookingSpec
import pragma.inventory.ext.ExtInstancedItem
import pragma.inventory.ext.ExtInstancedItemServerGrant
import pragma.inventory.ext.IncompleteFood
import pragma.utils.TimeProxy

class CookingCrafter(val timeProxy: TimeProxy) {

    fun meetsRequirements(
        cookingCompleteRequirements: CookingCompleteRequirements,
        destroyedInstancedItems: List<InstancedItem>,
    ): List<String> {
        val errors = mutableListOf<String>()
        try {
            val incompleteFood = destroyedInstancedItems.single()
            val incompleteFoodExt: ExtInstancedItem = incompleteFood.ext
            val completeTimeMillis =
                incompleteFoodExt.incompleteFood.timestampMillis

            if (timeProxy.currentEpochMillis() < completeTimeMillis) {
                errors.add("Food not cooked yet.")
            } else if (
                destroyedInstancedItems.single().catalogId !=
                    cookingCompleteRequirements.consumedCatalogId
            ) {
                errors.add("Cooking Complete Requirement not met.")
            }
        } catch (e: RuntimeException) {
            errors.add(
                "Not the correct number of IncompleteFoods were submitted for cooking."
            )
        }

        return errors
    }

    fun startCooking(cookingSpec: CookingSpec): CraftingPlugin.CraftResult {
        val catalogId = cookingSpec.catalogIdToCreate
        val readyTime =
            cookingSpec.cookTimeInSeconds * 1000 +
                timeProxy.currentEpochMillis()
        val incompleteFood =
            IncompleteFood.newBuilder().setTimestampMillis(readyTime)

        return CraftingPlugin.CraftResult(
            instancedItemServerGrants =
                listOf(
                    InstancedItemServerGrant(
                        ExtInstancedItemServerGrant.newBuilder()
                            .setIncompleteFood(incompleteFood)
                            .build(),
                        catalogId,
                        listOf()
                    )
                ),
            stackableItemGrants = listOf()
        )
    }

    fun completeCooking(
        cookingCompleteSpec: CookingCompleteSpec,
        inventoryContent: InventoryServiceContent
    ): CraftingPlugin.CraftResult {
        val instancedList = mutableListOf<InstancedItemServerGrant>()
        val stackableList = mutableListOf<StackableItemGrant>()
        if (
            inventoryContent.instancedSpecs.contains(
                cookingCompleteSpec.catalogIdToCreate
            )
        ) {
            instancedList.add(
                InstancedItemServerGrant(
                    ExtInstancedItemServerGrant.getDefaultInstance(),
                    cookingCompleteSpec.catalogIdToCreate,
                    listOf()
                )
            )
        } else {
            stackableList.add(
                StackableItemGrant(
                    cookingCompleteSpec.catalogIdToCreate,
                    1,
                    listOf()
                )
            )
        }
        return CraftingPlugin.CraftResult(instancedList, stackableList)
    }
}

The DemoCraftingPluginclass sits between Pragma Engine and the CookingCrafter class.

Pragma Engine calls DemoCraftingPlugin’s methods, then DemoCraftingPlugin calls the correct CookingCrafter methods.

Note: While not demonstrated in this example, the DemoCraftingPlugin class is also necessary for games that contain multiple types of crafting which are implemented in different classes.

  1. Create another kotlin file in this directory named DemoInstancedItemPlugin.kt. Replace the contents with the following code:
package cooking

import pragma.inventory.*
import pragma.content.ContentDataNodeService
import pragma.inventory.ext.ExtInstancedItem
import pragma.inventory.ext.ExtInstancedItemUpdate
import pragma.inventory.ext.ExtInstancedItemServerGrant
import pragma.inventory.ext.ExtInstancedItemServerUpdate
import pragma.inventory.ext.ExtPurchaseRequest
import pragma.services.Service

class DemoInstancedItemPlugin(val service: Service, val contentDataNodeService: ContentDataNodeService) :
    InstancedItemPlugin {

    override fun newInstanced(
        instancedSpec: InventoryContent.InstancedSpec,
        inventoryContent: InventoryServiceContent,
        startingInventory: InventoryData,
        pendingInventory: InventoryData,
        clientRequestExt: ExtPurchaseRequest?,
        serverRequestExt: ExtInstancedItemServerGrant?
    ): InstancedItemPlugin.InstancedItemPluginResult {
        if (clientRequestExt != null) {
            return InstancedItemPlugin.InstancedItemPluginResult(
                extInstancedItem = newClientItem()
            )
        }
        return InstancedItemPlugin.InstancedItemPluginResult(
            extInstancedItem = newServerItem(serverRequestExt!!)
        )
    }

    override fun update(
        initialInstancedItem: InstancedItem,
        instancedSpec: InventoryContent.InstancedSpec,
        updateEntry: InventoryContent.UpdateEntry,
        inventoryContent: InventoryServiceContent,
        startingInventory: InventoryData,
        pendingInventory: InventoryData,
        clientRequestExt: ExtInstancedItemUpdate?,
        serverRequestExt: ExtInstancedItemServerUpdate?
    ): InstancedItemPlugin.InstancedItemPluginResult {
        return InstancedItemPlugin.InstancedItemPluginResult(
            ExtInstancedItem.getDefaultInstance()
        )
    }

    private fun newClientItem(): ExtInstancedItem {
        val builder = ExtInstancedItem.newBuilder()
        return builder.build()
    }

    private fun newServerItem(
        requestExt: ExtInstancedItemServerGrant
    ): ExtInstancedItem {
        val builder = ExtInstancedItem.newBuilder()
        if (requestExt.hasIncompleteFood()) {
            builder.incompleteFoodBuilder.timestampMillis = requestExt.incompleteFood.timestampMillis
        }
        return builder.build()
    }
}

In this example, we use DemoInstancedItemPlugin to create our incomplete muffin. Incomplete muffins are instanced items which use the ext field to store completion time requirements.

  1. Register your plugins by editing local-dev.yml. Ensure the game section of the config file contains the following pluginConfigs:
game:
  pluginConfigs:
    InventoryService.craftingPlugin:
      class: "cooking.DemoCraftingPlugin"
    InventoryService.instancedItemPlugin:
      class: "cooking.DemoInstancedItemPlugin"

If the pluginConfigs section does not exist, you may have to create it.

  1. Run make ext using a terminal from the platform directory.