Creating the Battlepass Plugin #
In this section, we’ll be creating the plugin that powers the battlepass logic, and then registering it in the config.
Create an instanced item plugin #
First we’ll create a Pragma Engine plugin that initializes the battlepass and defines the leveling logic.
- Create new directories at
5-ext/ext/src/main/kotlin/myDemo/inventory
. - Create a Kotlin file at
5-ext/ext/src/main/kotlin/myDemo/inventory/MyInstancedItemPlugin.kt
. - Define a class that implements the
InstancedItemPlugin
interface:
@file:Suppress("unused")
package myDemo.inventory
import pragma.ExtError
import pragma.ExtException
import pragma.content.ContentDataNodeService
import pragma.inventory.InstancedItem
import pragma.inventory.InstancedItemPlugin
import pragma.inventory.InstancedItemServerGrant
import pragma.inventory.InventoryContent
import pragma.inventory.InventoryData
import pragma.inventory.InventoryServiceContent
import pragma.inventory.StackableItemGrant
import pragma.inventory.ext.Battlepass
import pragma.inventory.ext.BattlepassSpec
import pragma.inventory.ext.ExtInstancedItem
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.Reward
import pragma.services.Service
import pragma.utils.TimeProxy
@Suppress("UNUSED_PARAMETER")
class MyInstancedItemPlugin(private val timeProxy: TimeProxy = TimeProxy()) : InstancedItemPlugin {
constructor(service: Service, contentDataNodeService: ContentDataNodeService) : this()
override fun newInstanced(
instancedSpec: InventoryContent.InstancedSpec,
inventoryContent: InventoryServiceContent,
startingInventory: InventoryData,
pendingInventory: InventoryData,
clientRequestExt: ExtPurchaseRequest?,
serverRequestExt: ExtInstancedItemServerGrant?
): InstancedItemPlugin.InstancedItemPluginResult {
val extSpec: ExtInstancedSpec = instancedSpec.ext
val newitem = ExtInstancedItem.newBuilder()
if (extSpec.dataCase == ExtInstancedSpec.DataCase.BATTLEPASS_SPEC) {
val battlepass = Battlepass.newBuilder().setLevel(0).setXp(0)
newitem.setBattlepass(battlepass)
}
return InstancedItemPlugin.InstancedItemPluginResult(newitem.build())
}
override fun update(
initialInstancedItem: InstancedItem,
instancedSpec: InventoryContent.InstancedSpec,
updateEntry: InventoryContent.UpdateEntry,
inventoryContent: InventoryServiceContent,
startingInventory: InventoryData,
pendingInventory: InventoryData,
clientRequestExt: ExtInstancedItemUpdate?,
serverRequestExt: ExtInstancedItemServerUpdate?
): InstancedItemPlugin.InstancedItemPluginResult {
val extSpec: ExtInstancedSpec = instancedSpec.ext
val extItem: ExtInstancedItem = initialInstancedItem.ext
val battlepassSpec: BattlepassSpec = extSpec.battlepassSpec
val battlepass = extItem.battlepass
// example for server battlepass update, because we trust incoming requests
// players should not be able to say "hey I'm going to give myself battlepass updates!"
// (clientRequestExt)
if (serverRequestExt != null) {
when (serverRequestExt.dataCase) {
ExtInstancedItemServerUpdate.DataCase.XP_UPDATE -> {
if (isBattlepassMaxedOut(battlepassSpec, battlepass)) {
return InstancedItemPlugin.InstancedItemPluginResult(extItem)
}
val instancedItemServerGrants = mutableListOf<InstancedItemServerGrant>()
val stackableItemGrants = mutableListOf<StackableItemGrant>()
var level = battlepass.level.toInt()
var xp = (battlepass.xp + serverRequestExt.xpUpdate.amount).toInt()
while (isLevelComplete(battlepassSpec, level, xp)) {
toPragmaReward(battlepassSpec.levelsList[level].rewardsList).apply {
instancedItemServerGrants += this.first
stackableItemGrants += this.second
}
if (!isMaxLevel(battlepassSpec, level)) {
xp -= getLevelXpToComplete(battlepassSpec, level)
level += 1
} else {
xp = getLevelXpToComplete(battlepassSpec, level)
break
}
}
val updatedBattlepass =
battlepass.toBuilder().setXp(xp.toLong()).setLevel(level.toLong())
val updatedExt = extItem.toBuilder().setBattlepass(updatedBattlepass).build()
return InstancedItemPlugin.InstancedItemPluginResult(
updatedExt,
instancedItemServerGrants = instancedItemServerGrants,
stackableItemGrants = stackableItemGrants
)
}
ExtInstancedItemServerUpdate.DataCase.DATA_NOT_SET -> {
throw ExtException(
ExtError.UNKNOWN_EXT_ERROR,
"serverRequestExt does not contain battlepass update information"
)
}
else -> throw Exception("shouldn't get here")
}
} else {
throw ExtException(
ExtError.UNKNOWN_EXT_ERROR,
"Cannot process this request"
) // client request, or at least server not provided
}
}
private fun isBattlepassMaxedOut(
battlepassSpec: BattlepassSpec,
battlepass: Battlepass
): Boolean {
return isMaxLevel(battlepassSpec, battlepass.level.toInt()) &&
isLevelComplete(battlepassSpec, battlepass.level.toInt(), battlepass.xp.toInt())
}
private fun isMaxLevel(battlepassSpec: BattlepassSpec, level: Int): Boolean {
return level == battlepassSpec.levelsList.size - 1
}
private fun isLevelComplete(battlepassSpec: BattlepassSpec, level: Int, xp: Int): Boolean {
return xp >= getLevelXpToComplete(battlepassSpec, level)
}
private fun getLevelXpToComplete(battlepassSpec: BattlepassSpec, updatedLevel: Int) =
battlepassSpec.levelsList[updatedLevel].xpToComplete
fun findActive(
battlepassSpecs: List<InventoryContent.InstancedSpec>
): InventoryContent.InstancedSpec? {
return battlepassSpecs.firstOrNull() {
val ext: ExtInstancedSpec = it.ext
isActive(ext.battlepassSpec)
}
}
private fun isActive(battlepassSpec: BattlepassSpec): Boolean {
val currentTime = timeProxy.currentEpochMillis()
return currentTime >= battlepassSpec.startUnixTimestamp &&
currentTime <= battlepassSpec.endUnixTimestamp
}
private fun toPragmaReward(
rewards: List<Reward>
): Pair<List<InstancedItemServerGrant>, List<StackableItemGrant>> {
return rewards.fold(
mutableListOf<InstancedItemServerGrant>() to mutableListOf<StackableItemGrant>()
) { acc, it ->
when (it.itemCase) {
Reward.ItemCase.INSTANCED -> {
acc.first.add(
InstancedItemServerGrant(
ExtInstancedItemServerGrant.getDefaultInstance(),
it.instanced.catalogId,
listOf()
)
)
}
Reward.ItemCase.STACKABLE -> {
acc.second.add(
StackableItemGrant(it.stackable.catalogId, it.stackable.amount, listOf())
)
}
else -> {}
}
acc
}
}
}
- Register your plugins by editing
5-ext/config/local-dev.yml
. Ensure the game section of the config file matches the following:
pluginConfigs:
InventoryService.instancedItemPlugin:
class: "myDemo.inventory.MyInstancedItemPlugin"
- Run
make ext
using a terminal from theplatform
directory.