Building the Cooking Plugins #
In this section, we’ll build the plugins for cooking.
- Navigate to
platform/5-ext/ext/src/main/kotlin/
in IntelliJ’s Project view. - (if necessary) If the icon for the
kotlin
folder isn’t blue, right click it and select Mark directory as ➨ Sources root. - Right click the
kotlin
folder and chose New ➨ Package. Name itcooking
. - In the
cooking
folder, choose New ➨ Kotlin class/file and name itCookingCrafter.kt
. Replace the contents 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 withincompleteFoodExt
below.
package cooking
import pragma.inventory.*
import pragma.inventory.ext.ExtInstancedItem
import pragma.inventory.ext.ExtInstancedItemServerGrant
import pragma.inventory.ext.IncompleteFood
import pragma.inventory.ext.CookingCompleteRequirements
import pragma.inventory.ext.CookingCompleteSpec
import pragma.inventory.ext.CookingSpec
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(
listOf(
InstancedItemServerGrant(
ExtInstancedItemServerGrant.newBuilder()
.setIncompleteFood(incompleteFood)
.build(),
catalogId,
listOf()
)
), 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
)
}
}
- Create another
kotlin
file in this directory namedDemoCraftingPlugin.kt
. Replace the contents 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.
package cooking
import 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.ExtCraftRequest
import pragma.inventory.ext.ExtCraftingEntry
import pragma.inventory.ext.ExtPurchaseRequirements
import pragma.playerprogression.ProgressionData
import pragma.services.Service
import pragma.utils.TimeProxy
class DemoCraftingPlugin(
val service: Service,
val contentDataNodeService: ContentDataNodeService,
) : CraftingPlugin {
private val cookingCrafter = CookingCrafter(TimeProxy.defaultInstance)
override fun meetsRequirements(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
inventoryData: InventoryData,
progressionData: ProgressionData,
ext: ExtCraftRequest
): PragmaResult<Unit, List<String>> {
val extCraftingRequirements: ExtPurchaseRequirements = craftingEntry.requirements.ext
val errors = mutableListOf<String>()
when (extCraftingRequirements.dataCase) {
ExtPurchaseRequirements.DataCase.COOKING_COMPLETE_REQUIREMENTS -> {
errors.addAll(
cookingCrafter.meetsRequirements(
extCraftingRequirements.cookingCompleteRequirements,
destroyedInstancedItems
)
)
}
else -> {
// this is an intentional no-op
}
}
if (errors.isNotEmpty())
return PragmaResultError(errors)
return PragmaResultResponse(Unit)
}
override fun craft(
craftingEntry: CraftingEntryWrapper,
destroyedInstancedItems: List<InstancedItem>,
inventoryContent: InventoryServiceContent,
requestExt: ExtCraftRequest
): CraftingPlugin.CraftResult {
val extCraftingEntry: ExtCraftingEntry = craftingEntry.ext
return when (extCraftingEntry.dataCase) {
ExtCraftingEntry.DataCase.COOKING_SPEC -> {
cookingCrafter.startCooking(extCraftingEntry.cookingSpec)
}
ExtCraftingEntry.DataCase.COOKING_COMPLETE_SPEC -> {
cookingCrafter.completeCooking(extCraftingEntry.cookingCompleteSpec, inventoryContent)
}
else -> {
throw PragmaException(PragmaError.UNKNOWN_ERROR, "An unknown error occurred.")
}
}
}
}
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.
- 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
import pragma.utils.RandomProxy
class DemoInstancedItemPlugin(val service: Service, val contentDataNodeService: ContentDataNodeService) :
InstancedItemPlugin {
private lateinit var randomProxy: RandomProxy
override fun init(randomProxy: RandomProxy) {
this.randomProxy = randomProxy
}
override fun newInstanced(
instancedSpec: InventoryContent.InstancedSpec,
clientRequestExt: ExtPurchaseRequest?,
serverRequestExt: ExtInstancedItemServerGrant?
): ExtInstancedItem {
if (clientRequestExt != null) {
return newClientItem()
}
return newServerItem(serverRequestExt!!)
}
override fun update(
initialInstancedItem: InstancedItem,
instancedSpec: InventoryContent.InstancedSpec,
updateEntry: InventoryContent.UpdateEntry,
inventoryContent: InventoryServiceContent,
clientRequestExt: ExtInstancedItemUpdate?,
serverRequestExt: ExtInstancedItemServerUpdate?
): InstancedItemPlugin.UpdateResult {
return InstancedItemPlugin.UpdateResult(
ExtInstancedItem.getDefaultInstance(),
emptyList()
)
}
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.
- Register your plugins by editing
5-ext/config/LocalConfig.yml
. Ensure the game section of the config file matches the following:
game:
core:
clusterName: "local-game"
pluginConfigs:
InventoryService.craftingPlugin:
class: "cooking.DemoCraftingPlugin"
InventoryService.instancedItemPlugin:
class: "cooking.DemoInstancedItemPlugin"
- Run
make ext
using a terminal from theplatform
directory.