{
  "name": "playerProgressionTracker",
  "author": "Limon",
  "supportedGames": [
    "all"
  ],
  "takaroVersion": "main",
  "versions": [
    {
      "tag": "0.0.1",
      "description": "A comprehensive player progression system with levels, XP tracking, and configurable rewards. Players gain XP from kills and playtime, with permission-based multipliers for VIP/donor tiers. Each level can reward currency and items.",
      "configSchema": "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"levels\":{\"type\":\"array\",\"title\":\"Progression Levels\",\"description\":\"Configure levels with XP requirements and rewards\",\"items\":{\"type\":\"object\",\"properties\":{\"level\":{\"type\":\"number\",\"title\":\"Level Number\",\"minimum\":1},\"xpRequired\":{\"type\":\"number\",\"title\":\"XP Required\",\"description\":\"Total XP needed to reach this level\",\"minimum\":0},\"name\":{\"type\":\"string\",\"title\":\"Level Name\",\"description\":\"Optional title for this level (e.g., 'Novice', 'Expert')\"},\"currencyReward\":{\"type\":\"number\",\"title\":\"Currency Reward\",\"description\":\"Amount of currency to award on level up\",\"minimum\":0},\"itemRewards\":{\"type\":\"array\",\"title\":\"Item Rewards\",\"description\":\"Items to give when reaching this level\",\"items\":{\"type\":\"object\",\"properties\":{\"itemCode\":{\"type\":\"string\",\"title\":\"Item Code\",\"description\":\"Game item code (e.g., 'woodenBow', 'ammo9mmBulletBall')\"},\"amount\":{\"type\":\"number\",\"title\":\"Amount\",\"minimum\":1},\"quality\":{\"type\":\"string\",\"title\":\"Quality\",\"description\":\"Item quality (if applicable)\"}},\"required\":[\"itemCode\",\"amount\"]}}},\"required\":[\"level\",\"xpRequired\"]}},\"xpSettings\":{\"type\":\"object\",\"title\":\"XP Settings\",\"properties\":{\"baseKillXP\":{\"type\":\"number\",\"title\":\"Base Kill XP\",\"description\":\"Default XP for killing an entity\",\"minimum\":1,\"default\":10},\"xpPerHourPlayed\":{\"type\":\"number\",\"title\":\"XP Per Hour Played\",\"description\":\"XP awarded per hour of active playtime\",\"minimum\":0,\"default\":100},\"activePlayerMinutes\":{\"type\":\"number\",\"title\":\"Active Check Interval\",\"description\":\"Minutes between active player XP awards\",\"minimum\":1,\"default\":5},\"entityXPOverrides\":{\"type\":\"object\",\"title\":\"Entity-Specific XP\",\"description\":\"Override XP for specific entity types\",\"additionalProperties\":{\"type\":\"number\",\"minimum\":0}}}},\"messages\":{\"type\":\"object\",\"title\":\"Messages\",\"properties\":{\"levelUpMessage\":{\"type\":\"string\",\"title\":\"Level Up Message\",\"description\":\"Message shown when player levels up. Use {playerName}, {level}, {levelName}\",\"default\":\"🎉 {playerName} reached Level {level} - {levelName}!\"},\"progressCommandFormat\":{\"type\":\"string\",\"title\":\"Progress Format\",\"description\":\"Format for progression display\",\"default\":\"Level {level}: {currentXP}/{requiredXP} XP | Next: {xpNeeded} XP\"},\"rewardClaimedMessage\":{\"type\":\"string\",\"title\":\"Reward Claimed\",\"default\":\"✅ Level {level} rewards claimed!\"}}}},\"required\":[\"levels\",\"xpSettings\"]}",
      "uiSchema": "{\"levels\":{\"ui:help\":\"Define progression levels with XP thresholds and rewards\",\"items\":{\"level\":{\"ui:help\":\"Level number (1, 2, 3, etc.)\"},\"xpRequired\":{\"ui:help\":\"Total XP needed to reach this level\"},\"currencyReward\":{\"ui:help\":\"Currency to award when reaching this level\"},\"itemRewards\":{\"ui:help\":\"Items to give as rewards\",\"items\":{\"itemCode\":{\"ui:help\":\"The game's internal item code\"},\"amount\":{\"ui:help\":\"How many of this item to give\"}}}}},\"xpSettings\":{\"ui:help\":\"Configure how players earn XP\",\"baseKillXP\":{\"ui:help\":\"XP earned for each kill (before multipliers)\"},\"xpPerHourPlayed\":{\"ui:help\":\"XP earned per hour of active play\"},\"activePlayerMinutes\":{\"ui:help\":\"How often to check and award active player XP\"},\"entityXPOverrides\":{\"ui:help\":\"Set custom XP values for specific entities (e.g., 'zombie': 15, 'bear': 50)\"}}}",
      "commands": [
        {
          "name": "progression",
          "trigger": "progression",
          "helpText": "Shows your current level, XP, playtime, and progress to next level",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, player, pog, module } = data;\n    \n    try {\n        if (!player) {\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: 'Error: Player information not available',\n                opts: { recipient: { gameId: pog.gameId } }\n            });\n            return;\n        }\n        \n        const config = module.userConfig;\n        const levels = config.levels || [];\n        const messages = config.messages || {};\n        \n        // Get player variables\n        const variablesSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId]\n            }\n        });\n        \n        let playerXP = 0;\n        let playerLevel = 0;\n        let totalPlaytimeMinutes = 0;\n        let killStats = {};\n        \n        // Parse variables\n        for (const variable of variablesSearch.data.data) {\n            switch (variable.key) {\n                case 'playerXP':\n                    playerXP = parseInt(variable.value) || 0;\n                    break;\n                case 'playerLevel':\n                    playerLevel = parseInt(variable.value) || 0;\n                    break;\n                case 'totalPlaytimeMinutes':\n                    totalPlaytimeMinutes = parseInt(variable.value) || 0;\n                    break;\n                default:\n                    if (variable.key.startsWith('totalKills_')) {\n                        const entityName = variable.key.replace('totalKills_', '');\n                        killStats[entityName] = parseInt(variable.value) || 0;\n                    }\n                    break;\n            }\n        }\n        \n        // Calculate current level inline (same logic as checkLevelUp function)\n        const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);\n        \n        let currentLevel = 0;\n        let currentLevelData = null;\n        let nextLevelData = null;\n        \n        for (let i = 0; i < sortedLevels.length; i++) {\n            if (playerXP >= sortedLevels[i].xpRequired) {\n                currentLevel = sortedLevels[i].level;\n                currentLevelData = sortedLevels[i];\n            } else {\n                nextLevelData = sortedLevels[i];\n                break;\n            }\n        }\n        \n        const xpToNextLevel = nextLevelData ? nextLevelData.xpRequired - playerXP : 0;\n        \n        // Format playtime\n        const hours = Math.floor(totalPlaytimeMinutes / 60);\n        const minutes = totalPlaytimeMinutes % 60;\n        const playtimeText = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;\n        \n        // Build progression message\n        let progressMessage = `=== ${player.name}'s Progression ===\\\\n`;\n        progressMessage += `Level: ${currentLevel}`;\n        \n        if (currentLevelData && currentLevelData.name) {\n            progressMessage += ` - ${currentLevelData.name}`;\n        }\n        \n        progressMessage += `\\\\nXP: ${playerXP}`;\n        \n        if (nextLevelData) {\n            progressMessage += ` / ${nextLevelData.xpRequired}`;\n            progressMessage += `\\\\nNext Level: ${xpToNextLevel} XP needed`;\n            if (nextLevelData.name) {\n                progressMessage += ` (${nextLevelData.name})`;\n            }\n        } else {\n            progressMessage += `\\\\n🏆 Max level reached!`;\n        }\n        \n        progressMessage += `\\\\nPlaytime: ${playtimeText}`;\n        \n        // Add kill stats\n        if (Object.keys(killStats).length > 0) {\n            progressMessage += `\\\\n\\\\nKills:`;\n            const sortedKills = Object.entries(killStats)\n                .sort(([,a], [,b]) => b - a)\n                .slice(0, 5); // Top 5\n            \n            for (const [entity, count] of sortedKills) {\n                progressMessage += `\\\\n  ${entity}: ${count}`;\n            }\n        }\n        \n        // Show next level rewards\n        if (nextLevelData) {\n            progressMessage += `\\\\n\\\\nNext Level Rewards:`;\n            if (nextLevelData.currencyReward > 0) {\n                progressMessage += `\\\\n  💰 ${nextLevelData.currencyReward} currency`;\n            }\n            if (nextLevelData.itemRewards && nextLevelData.itemRewards.length > 0) {\n                for (const item of nextLevelData.itemRewards) {\n                    progressMessage += `\\\\n  📦 ${item.amount}x ${item.itemCode}`;\n                }\n            }\n        }\n        \n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: progressMessage,\n            opts: { recipient: { gameId: player.gameId } }\n        });\n        \n        console.log(`Progression stats shown to ${player.name}: Level ${currentLevel}, XP ${playerXP}`);\n        \n    } catch (error) {\n        console.error('Error in progression command:', error);\n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: 'Error retrieving progression data. Please try again.',\n            opts: { recipient: { gameId: pog.gameId } }\n        });\n    }\n}\n\nawait main();",
          "arguments": []
        },
        {
          "name": "claim",
          "trigger": "claim",
          "helpText": "Claims any unclaimed level rewards you have earned",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, player, pog, module } = data;\n    \n    try {\n        if (!player) {\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: 'Error: Player information not available',\n                opts: { recipient: { gameId: pog.gameId } }\n            });\n            return;\n        }\n        \n        const config = module.userConfig;\n        const levels = config.levels || [];\n        const messages = config.messages || {};\n        \n        // Get player XP\n        const xpVarSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId],\n                key: ['playerXP']\n            }\n        });\n        \n        let playerXP = 0;\n        if (xpVarSearch.data.data.length > 0) {\n            playerXP = parseInt(xpVarSearch.data.data[0].value) || 0;\n        }\n        \n        // Get level info\n        const levelResult = await takaro.function.functionControllerTrigger('6256101e-831c-4a82-b93b-54978ed1f29e', {\n            playerId: player.id,\n            gameServerId,\n            currentXP: playerXP,\n            module\n        });\n        \n        const currentLevel = levelResult.data.data.currentLevel;\n        \n        if (currentLevel === 0) {\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: 'You haven\\'t reached any levels yet! Keep playing to gain XP.',\n                opts: { recipient: { gameId: player.gameId } }\n            });\n            return;\n        }\n        \n        // Get all claimed rewards\n        const claimedVarsSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId]\n            }\n        });\n        \n        const claimedLevels = new Set();\n        for (const variable of claimedVarsSearch.data.data) {\n            if (variable.key.startsWith('claimedRewards_')) {\n                const level = parseInt(variable.key.replace('claimedRewards_', ''));\n                claimedLevels.add(level);\n            }\n        }\n        \n        // Sort levels by level number\n        const sortedLevels = levels.sort((a, b) => a.level - b.level);\n        \n        // Find unclaimed levels\n        const unclaimedLevels = [];\n        for (const levelData of sortedLevels) {\n            if (levelData.level <= currentLevel && !claimedLevels.has(levelData.level)) {\n                unclaimedLevels.push(levelData);\n            }\n        }\n        \n        if (unclaimedLevels.length === 0) {\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: 'No unclaimed rewards available. All your level rewards have been claimed!',\n                opts: { recipient: { gameId: player.gameId } }\n            });\n            return;\n        }\n        \n        // Claim all unclaimed rewards\n        let totalCurrency = 0;\n        let allItems = [];\n        \n        for (const levelData of unclaimedLevels) {\n            try {\n                // Grant rewards\n                const rewardResult = await takaro.function.functionControllerTrigger('5fcae3b7-f95c-4e43-bdd9-ccb32d570d24', {\n                    playerId: player.id,\n                    gameServerId,\n                    levelData,\n                    module,\n                    playerName: player.name\n                });\n                \n                // Track rewards for summary\n                if (levelData.currencyReward > 0) {\n                    totalCurrency += levelData.currencyReward;\n                }\n                \n                if (levelData.itemRewards && levelData.itemRewards.length > 0) {\n                    allItems.push(...levelData.itemRewards);\n                }\n                \n                console.log(`Claimed level ${levelData.level} rewards for ${player.name}`);\n                \n            } catch (rewardError) {\n                console.error(`Error claiming level ${levelData.level} rewards:`, rewardError);\n            }\n        }\n        \n        // Send summary message\n        let summaryMessage = `✅ Claimed rewards for ${unclaimedLevels.length} levels!`;\n        \n        if (totalCurrency > 0) {\n            summaryMessage += `\\n💰 Total currency: ${totalCurrency}`;\n        }\n        \n        if (allItems.length > 0) {\n            const itemSummary = {};\n            for (const item of allItems) {\n                const key = `${item.itemCode}${item.quality ? ` (${item.quality})` : ''}`;\n                itemSummary[key] = (itemSummary[key] || 0) + item.amount;\n            }\n            \n            summaryMessage += `\\n📦 Items received:`;\n            for (const [itemDesc, count] of Object.entries(itemSummary)) {\n                summaryMessage += `\\n  ${count}x ${itemDesc}`;\n            }\n        }\n        \n        const levelsText = unclaimedLevels.map(l => l.level).join(', ');\n        summaryMessage += `\\n\\nLevels claimed: ${levelsText}`;\n        \n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: summaryMessage,\n            opts: { recipient: { gameId: player.gameId } }\n        });\n        \n        console.log(`${player.name} claimed rewards for levels: ${levelsText}`);\n        \n    } catch (error) {\n        console.error('Error in claim command:', error);\n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: 'Error claiming rewards. Please try again.',\n            opts: { recipient: { gameId: pog.gameId } }\n        });\n    }\n}\n\nawait main();",
          "arguments": []
        },
        {
          "name": "resetprogression",
          "trigger": "resetprogression",
          "helpText": "Resets progression data for a player or all players (Admin only)",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, player, pog, module, arguments: args } = data;\n    \n    try {\n        if (!player) {\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: 'Error: Player information not available',\n                opts: { recipient: { gameId: pog.gameId } }\n            });\n            return;\n        }\n        \n        const targetPlayer = args.target || '';\n        \n        if (targetPlayer.toLowerCase() === 'all') {\n            // Reset all players on this server\n            await resetAllPlayers(gameServerId, module.moduleId, player.name);\n        } else if (targetPlayer.trim() !== '') {\n            // Reset specific player\n            await resetSpecificPlayer(gameServerId, module.moduleId, targetPlayer, player.name);\n        } else {\n            // No argument provided, show help\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: 'Usage: @resetprogression <playerName> or @resetprogression all\\\\nResets progression data for specified player or all players.',\n                opts: { recipient: { gameId: player.gameId } }\n            });\n            return;\n        }\n        \n    } catch (error) {\n        console.error('Error in reset progression command:', error);\n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: 'Error resetting progression data. Please try again.',\n            opts: { recipient: { gameId: pog.gameId } }\n        });\n    }\n}\n\nasync function resetAllPlayers(gameServerId, moduleId, adminName) {\n    try {\n        // Find all variables for this module on this server\n        const allVariables = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                moduleId: [moduleId]\n            },\n            limit: 1000 // Get many variables\n        });\n        \n        const progressionKeys = ['playerXP', 'playerLevel', 'totalPlaytimeMinutes'];\n        let deletedCount = 0;\n        let playerCount = 0;\n        const processedPlayers = new Set();\n        \n        // Delete progression variables for all players\n        for (const variable of allVariables.data.data) {\n            const isProgressionVar = progressionKeys.includes(variable.key) || \n                                   variable.key.startsWith('totalKills_') || \n                                   variable.key.startsWith('claimedRewards_');\n            \n            if (isProgressionVar) {\n                await takaro.variable.variableControllerDelete(variable.id);\n                deletedCount++;\n                \n                if (!processedPlayers.has(variable.playerId)) {\n                    processedPlayers.add(variable.playerId);\n                    playerCount++;\n                }\n            }\n        }\n        \n        // Send confirmation message\n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: `🔄 Admin ${adminName} reset progression data for ALL PLAYERS\\\\n✅ Reset ${playerCount} players (${deletedCount} variables cleared)\\\\n⚠️ All XP, levels, playtime, and kill stats have been wiped!`\n        });\n        \n        console.log(`Admin ${adminName} reset progression for ${playerCount} players (${deletedCount} variables deleted)`);\n        \n    } catch (error) {\n        console.error('Error resetting all players:', error);\n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: 'Error resetting all player data. Please try again.'\n        });\n    }\n}\n\nasync function resetSpecificPlayer(gameServerId, moduleId, targetPlayerName, adminName) {\n    try {\n        // Find the target player by name\n        const playerSearch = await takaro.player.playerControllerSearch({\n            search: { name: [targetPlayerName] },\n            limit: 10\n        });\n        \n        if (!playerSearch.data.data || playerSearch.data.data.length === 0) {\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: `❌ Player \"${targetPlayerName}\" not found. Make sure the name is spelled correctly.`\n            });\n            return;\n        }\n        \n        // Find exact match or closest match\n        let targetPlayer = null;\n        for (const player of playerSearch.data.data) {\n            if (player.name.toLowerCase() === targetPlayerName.toLowerCase()) {\n                targetPlayer = player;\n                break;\n            }\n        }\n        \n        if (!targetPlayer) {\n            // Use first result if no exact match\n            targetPlayer = playerSearch.data.data[0];\n        }\n        \n        // Find all variables for this specific player\n        const playerVariables = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [targetPlayer.id],\n                moduleId: [moduleId]\n            },\n            limit: 100\n        });\n        \n        const progressionKeys = ['playerXP', 'playerLevel', 'totalPlaytimeMinutes'];\n        let deletedCount = 0;\n        \n        // Delete progression variables for this player\n        for (const variable of playerVariables.data.data) {\n            const isProgressionVar = progressionKeys.includes(variable.key) || \n                                   variable.key.startsWith('totalKills_') || \n                                   variable.key.startsWith('claimedRewards_');\n            \n            if (isProgressionVar) {\n                await takaro.variable.variableControllerDelete(variable.id);\n                deletedCount++;\n            }\n        }\n        \n        // Send confirmation message\n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: `🔄 Admin ${adminName} reset progression data for ${targetPlayer.name}\\\\n✅ Cleared ${deletedCount} progression variables\\\\n⚠️ ${targetPlayer.name}'s XP, level, playtime, and kill stats have been reset!`\n        });\n        \n        console.log(`Admin ${adminName} reset progression for ${targetPlayer.name} (${deletedCount} variables deleted)`);\n        \n    } catch (error) {\n        console.error('Error resetting specific player:', error);\n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: `Error resetting data for \"${targetPlayerName}\". Please try again.`\n        });\n    }\n}\n\nawait main();",
          "arguments": [
            {
              "name": "target",
              "type": "string",
              "helpText": "Player name or 'all' to reset everyone",
              "defaultValue": "",
              "position": 0
            }
          ]
        }
      ],
      "hooks": [
        {
          "name": "Entity Kill XP",
          "eventType": "entity-killed",
          "description": "Hook for entity-killed events",
          "regex": ".*",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, eventData, player, module } = data;\n    \n    try {\n        if (!player) {\n            console.log('No player data in entity kill event');\n            return;\n        }\n        \n        const config = module.userConfig;\n        const xpSettings = config.xpSettings || {};\n        const baseKillXP = xpSettings.baseKillXP || 10;\n        const entityXPOverrides = xpSettings.entityXPOverrides || {};\n        \n        // Parse event data to get entity information\n        let entityName = 'unknown';\n        let baseXP = baseKillXP;\n        \n        if (eventData && eventData.entity) {\n            entityName = eventData.entity.toLowerCase();\n            \n            // Check for entity-specific XP override\n            if (entityXPOverrides[entityName] !== undefined) {\n                baseXP = entityXPOverrides[entityName];\n            }\n        }\n        \n        console.log(`Player ${player.name} killed ${entityName} - Base XP: ${baseXP}`);\n        \n        // Inline XP multiplier calculation (from calculateXPWithMultiplier function)\n        let multiplier = 1;\n        try {\n            const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id);\n            \n            if (playerInfo.data.data.roles && playerInfo.data.data.roles.length > 0) {\n                for (const roleAssignment of playerInfo.data.data.roles) {\n                    if (roleAssignment.role && roleAssignment.role.permissions) {\n                        for (const perm of roleAssignment.role.permissions) {\n                            if (perm.permission && perm.permission.permission === 'XP_MULTIPLIER' && perm.permission.moduleVersionId === module.versionId) {\n                                const count = perm.count || 0;\n                                if (count > multiplier) {\n                                    multiplier = count;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        } catch (permError) {\n            console.log(`Could not check permissions for ${player.name}, using 1x multiplier`);\n        }\n        \n        const finalXP = Math.floor(baseXP * multiplier);\n        \n        // Get current player XP\n        const xpVarSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId],\n                key: ['playerXP']\n            }\n        });\n        \n        let currentXP = 0;\n        if (xpVarSearch.data.data.length > 0) {\n            currentXP = parseInt(xpVarSearch.data.data[0].value) || 0;\n        }\n        \n        const newXP = currentXP + finalXP;\n        \n        // Update XP variable\n        if (xpVarSearch.data.data.length > 0) {\n            await takaro.variable.variableControllerUpdate(xpVarSearch.data.data[0].id, {\n                value: newXP.toString()\n            });\n        } else {\n            await takaro.variable.variableControllerCreate({\n                key: 'playerXP',\n                value: newXP.toString(),\n                gameServerId,\n                playerId: player.id,\n                moduleId: module.moduleId\n            });\n        }\n        \n        // Update kill counter for this entity type\n        const killKey = `totalKills_${entityName}`;\n        const killVarSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId],\n                key: [killKey]\n            }\n        });\n        \n        let currentKills = 0;\n        if (killVarSearch.data.data.length > 0) {\n            currentKills = parseInt(killVarSearch.data.data[0].value) || 0;\n        }\n        \n        const newKills = currentKills + 1;\n        \n        if (killVarSearch.data.data.length > 0) {\n            await takaro.variable.variableControllerUpdate(killVarSearch.data.data[0].id, {\n                value: newKills.toString()\n            });\n        } else {\n            await takaro.variable.variableControllerCreate({\n                key: killKey,\n                value: '1',\n                gameServerId,\n                playerId: player.id,\n                moduleId: module.moduleId\n            });\n        }\n        \n        // Inline level checking (from checkLevelUp function)\n        const levels = config.levels || [];\n        const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);\n        \n        let currentLevel = 0;\n        let currentLevelData = null;\n        let nextLevelData = null;\n        \n        for (let i = 0; i < sortedLevels.length; i++) {\n            if (newXP >= sortedLevels[i].xpRequired) {\n                currentLevel = sortedLevels[i].level;\n                currentLevelData = sortedLevels[i];\n            } else {\n                nextLevelData = sortedLevels[i];\n                break;\n            }\n        }\n        \n        // Get stored level from variables\n        const levelVarSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId],\n                key: ['playerLevel']\n            }\n        });\n        \n        const storedLevel = levelVarSearch.data.data.length > 0 \n            ? parseInt(levelVarSearch.data.data[0].value) \n            : 0;\n        \n        const leveledUp = currentLevel > storedLevel;\n        \n        // Update stored level if changed\n        if (leveledUp) {\n            if (levelVarSearch.data.data.length > 0) {\n                await takaro.variable.variableControllerUpdate(levelVarSearch.data.data[0].id, {\n                    value: currentLevel.toString()\n                });\n            } else {\n                await takaro.variable.variableControllerCreate({\n                    key: 'playerLevel',\n                    value: currentLevel.toString(),\n                    gameServerId,\n                    playerId: player.id,\n                    moduleId: module.moduleId\n                });\n            }\n            \n            // Inline reward granting (from grantRewards function)\n            if (currentLevelData) {\n                const messages = config.messages || {};\n                let rewardsGranted = [];\n                \n                // Grant currency reward\n                if (currentLevelData.currencyReward && currentLevelData.currencyReward > 0) {\n                    await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(\n                        gameServerId, \n                        player.id, \n                        { currency: currentLevelData.currencyReward }\n                    );\n                    rewardsGranted.push(`${currentLevelData.currencyReward} currency`);\n                }\n                \n                // Grant item rewards\n                if (currentLevelData.itemRewards && currentLevelData.itemRewards.length > 0) {\n                    for (const itemReward of currentLevelData.itemRewards) {\n                        try {\n                            await takaro.gameserver.gameServerControllerGiveItem(gameServerId, player.id, {\n                                name: itemReward.itemCode,\n                                amount: itemReward.amount,\n                                quality: itemReward.quality || ''\n                            });\n                            rewardsGranted.push(`${itemReward.amount}x ${itemReward.itemCode}`);\n                        } catch (itemError) {\n                            console.error(`Failed to grant item ${itemReward.itemCode}:`, itemError);\n                        }\n                    }\n                }\n                \n                // Mark rewards as claimed\n                const claimKey = `claimedRewards_${currentLevelData.level}`;\n                await takaro.variable.variableControllerCreate({\n                    key: claimKey,\n                    value: new Date().toISOString(),\n                    gameServerId,\n                    playerId: player.id,\n                    moduleId: module.moduleId\n                });\n                \n                // Send level up message\n                const levelUpMessage = messages.levelUpMessage || '🎉 {playerName} reached Level {level} - {levelName}!';\n                const formattedMessage = levelUpMessage\n                    .replace('{playerName}', player.name)\n                    .replace('{level}', currentLevelData.level.toString())\n                    .replace('{levelName}', currentLevelData.name || `Level ${currentLevelData.level}`);\n                \n                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                    message: formattedMessage\n                });\n                \n                if (rewardsGranted.length > 0) {\n                    const rewardsMessage = `Rewards: ${rewardsGranted.join(', ')}`;\n                    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                        message: rewardsMessage\n                    });\n                }\n                \n                console.log(`Level up! ${player.name} reached level ${currentLevel} from kill, rewards: ${rewardsGranted.join(', ')}`);\n            }\n        }\n        \n        console.log(`Kill XP awarded: ${player.name} gained ${finalXP} XP (${multiplier}x multiplier) for killing ${entityName}. Total XP: ${newXP}, Level: ${currentLevel}`);\n        \n    } catch (error) {\n        console.error('Error in entity kill XP hook:', error);\n    }\n}\n\nawait main();"
        },
        {
          "name": "Player Connected Playtime",
          "eventType": "player-connected",
          "description": "Hook for player-connected events",
          "regex": ".*",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, eventData, player, module } = data;\n    \n    try {\n        if (!player) {\n            console.log('No player data available');\n            return;\n        }\n        \n        console.log(`Player ${player.name} connected - updating playtime tracking`);\n        \n        // Store connection time for playtime XP calculation\n        await takaro.variable.variableControllerCreate({\n            key: 'lastConnectionTime',\n            value: new Date().toISOString(),\n            gameServerId,\n            playerId: player.id,\n            moduleId: module.moduleId\n        });\n        \n        // Initialize player XP if it doesn't exist\n        const xpVarSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId],\n                key: ['playerXP']\n            }\n        });\n        \n        if (xpVarSearch.data.data.length === 0) {\n            await takaro.variable.variableControllerCreate({\n                key: 'playerXP',\n                value: '0',\n                gameServerId,\n                playerId: player.id,\n                moduleId: module.moduleId\n            });\n            \n            console.log(`Initialized XP for new player ${player.name}`);\n        }\n        \n        // Initialize player level if it doesn't exist\n        const levelVarSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [player.id],\n                moduleId: [module.moduleId],\n                key: ['playerLevel']\n            }\n        });\n        \n        if (levelVarSearch.data.data.length === 0) {\n            await takaro.variable.variableControllerCreate({\n                key: 'playerLevel',\n                value: '0',\n                gameServerId,\n                playerId: player.id,\n                moduleId: module.moduleId\n            });\n        }\n        \n        console.log(`Player connection tracking updated for ${player.name}`);\n        \n    } catch (error) {\n        console.error('Error in player connected hook:', error);\n    }\n}\n\nawait main();"
        }
      ],
      "cronJobs": [
        {
          "name": "Active Player XP",
          "temporalValue": "*/5 * * * *",
          "description": "",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, module } = data;\n    \n    try {\n        const config = module.userConfig;\n        const xpSettings = config.xpSettings || {};\n        const xpPerHour = xpSettings.xpPerHourPlayed || 100;\n        const checkIntervalMinutes = xpSettings.activePlayerMinutes || 5;\n        \n        // Get all online players on this server\n        const playersResult = await takaro.gameserver.gameServerControllerGetPlayers(gameServerId);\n        \n        if (!playersResult.data.data || playersResult.data.data.length === 0) {\n            console.log('No online players found');\n            return;\n        }\n        \n        console.log(`Checking active XP for ${playersResult.data.data.length} online players`);\n        \n        for (const gamePlayer of playersResult.data.data) {\n            try {\n                const playerName = gamePlayer.name;\n                \n                // Find Takaro player ID by searching for players with matching identifiers\n                let playerSearchResult;\n                \n                if (gamePlayer.steamId) {\n                    playerSearchResult = await takaro.player.playerControllerSearch({\n                        filters: {\n                            steamId: [gamePlayer.steamId]\n                        },\n                        limit: 1\n                    });\n                } else if (gamePlayer.epicOnlineServicesId) {\n                    playerSearchResult = await takaro.player.playerControllerSearch({\n                        filters: {\n                            epicOnlineServicesId: [gamePlayer.epicOnlineServicesId]\n                        },\n                        limit: 1\n                    });\n                }\n                \n                if (!playerSearchResult || !playerSearchResult.data.data || playerSearchResult.data.data.length === 0) {\n                    console.log(`Could not find Takaro player for game player: ${playerName}`);\n                    continue;\n                }\n                \n                const playerId = playerSearchResult.data.data[0].id;\n                \n                // Calculate XP for the interval\n                const baseXPForInterval = Math.floor((xpPerHour / 60) * checkIntervalMinutes);\n                \n                if (baseXPForInterval <= 0) {\n                    continue;\n                }\n                \n                // Inline XP multiplier calculation (from calculateXPWithMultiplier function)\n                let multiplier = 1;\n                try {\n                    const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, playerId);\n                    \n                    if (playerInfo.data.data.roles && playerInfo.data.data.roles.length > 0) {\n                        for (const roleAssignment of playerInfo.data.data.roles) {\n                            if (roleAssignment.role && roleAssignment.role.permissions) {\n                                for (const perm of roleAssignment.role.permissions) {\n                                    if (perm.permission && perm.permission.permission === 'XP_MULTIPLIER' && perm.permission.moduleVersionId === module.versionId) {\n                                        const count = perm.count || 0;\n                                        if (count > multiplier) {\n                                            multiplier = count;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                } catch (permError) {\n                    console.log(`Could not check permissions for ${playerName}, using 1x multiplier`);\n                }\n                \n                const finalXP = Math.floor(baseXPForInterval * multiplier);\n                \n                // Get current player XP\n                const xpVarSearch = await takaro.variable.variableControllerSearch({\n                    filters: {\n                        gameServerId: [gameServerId],\n                        playerId: [playerId],\n                        moduleId: [module.moduleId],\n                        key: ['playerXP']\n                    }\n                });\n                \n                let currentXP = 0;\n                if (xpVarSearch.data.data.length > 0) {\n                    currentXP = parseInt(xpVarSearch.data.data[0].value) || 0;\n                }\n                \n                const newXP = currentXP + finalXP;\n                \n                // Update XP variable\n                if (xpVarSearch.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(xpVarSearch.data.data[0].id, {\n                        value: newXP.toString()\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'playerXP',\n                        value: newXP.toString(),\n                        gameServerId,\n                        playerId,\n                        moduleId: module.moduleId\n                    });\n                }\n                \n                // Update total playtime tracking\n                const playtimeVarSearch = await takaro.variable.variableControllerSearch({\n                    filters: {\n                        gameServerId: [gameServerId],\n                        playerId: [playerId],\n                        moduleId: [module.moduleId],\n                        key: ['totalPlaytimeMinutes']\n                    }\n                });\n                \n                let totalMinutes = 0;\n                if (playtimeVarSearch.data.data.length > 0) {\n                    totalMinutes = parseInt(playtimeVarSearch.data.data[0].value) || 0;\n                }\n                \n                const newTotalMinutes = totalMinutes + checkIntervalMinutes;\n                \n                if (playtimeVarSearch.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(playtimeVarSearch.data.data[0].id, {\n                        value: newTotalMinutes.toString()\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'totalPlaytimeMinutes',\n                        value: checkIntervalMinutes.toString(),\n                        gameServerId,\n                        playerId,\n                        moduleId: module.moduleId\n                    });\n                }\n                \n                // Inline level checking (from checkLevelUp function)\n                const levels = config.levels || [];\n                const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);\n                \n                let currentLevel = 0;\n                let currentLevelData = null;\n                let nextLevelData = null;\n                \n                for (let i = 0; i < sortedLevels.length; i++) {\n                    if (newXP >= sortedLevels[i].xpRequired) {\n                        currentLevel = sortedLevels[i].level;\n                        currentLevelData = sortedLevels[i];\n                    } else {\n                        nextLevelData = sortedLevels[i];\n                        break;\n                    }\n                }\n                \n                // Get stored level from variables\n                const levelVarSearch = await takaro.variable.variableControllerSearch({\n                    filters: {\n                        gameServerId: [gameServerId],\n                        playerId: [playerId],\n                        moduleId: [module.moduleId],\n                        key: ['playerLevel']\n                    }\n                });\n                \n                const storedLevel = levelVarSearch.data.data.length > 0 \n                    ? parseInt(levelVarSearch.data.data[0].value) \n                    : 0;\n                \n                const leveledUp = currentLevel > storedLevel;\n                \n                // Update stored level if changed\n                if (leveledUp) {\n                    if (levelVarSearch.data.data.length > 0) {\n                        await takaro.variable.variableControllerUpdate(levelVarSearch.data.data[0].id, {\n                            value: currentLevel.toString()\n                        });\n                    } else {\n                        await takaro.variable.variableControllerCreate({\n                            key: 'playerLevel',\n                            value: currentLevel.toString(),\n                            gameServerId,\n                            playerId,\n                            moduleId: module.moduleId\n                        });\n                    }\n                    \n                    // Inline reward granting (from grantRewards function)\n                    if (currentLevelData) {\n                        const messages = config.messages || {};\n                        let rewardsGranted = [];\n                        \n                        // Grant currency reward\n                        if (currentLevelData.currencyReward && currentLevelData.currencyReward > 0) {\n                            await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(\n                                gameServerId, \n                                playerId, \n                                { currency: currentLevelData.currencyReward }\n                            );\n                            rewardsGranted.push(`${currentLevelData.currencyReward} currency`);\n                        }\n                        \n                        // Grant item rewards\n                        if (currentLevelData.itemRewards && currentLevelData.itemRewards.length > 0) {\n                            for (const itemReward of currentLevelData.itemRewards) {\n                                try {\n                                    await takaro.gameserver.gameServerControllerGiveItem(gameServerId, playerId, {\n                                        name: itemReward.itemCode,\n                                        amount: itemReward.amount,\n                                        quality: itemReward.quality || ''\n                                    });\n                                    rewardsGranted.push(`${itemReward.amount}x ${itemReward.itemCode}`);\n                                } catch (itemError) {\n                                    console.error(`Failed to grant item ${itemReward.itemCode}:`, itemError);\n                                }\n                            }\n                        }\n                        \n                        // Mark rewards as claimed\n                        const claimKey = `claimedRewards_${currentLevelData.level}`;\n                        await takaro.variable.variableControllerCreate({\n                            key: claimKey,\n                            value: new Date().toISOString(),\n                            gameServerId,\n                            playerId,\n                            moduleId: module.moduleId\n                        });\n                        \n                        // Send level up message\n                        const levelUpMessage = messages.levelUpMessage || '🎉 {playerName} reached Level {level} - {levelName}!';\n                        const formattedMessage = levelUpMessage\n                            .replace('{playerName}', playerName || 'Player')\n                            .replace('{level}', currentLevelData.level.toString())\n                            .replace('{levelName}', currentLevelData.name || `Level ${currentLevelData.level}`);\n                        \n                        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                            message: formattedMessage\n                        });\n                        \n                        if (rewardsGranted.length > 0) {\n                            const rewardsMessage = `Rewards: ${rewardsGranted.join(', ')}`;\n                            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                                message: rewardsMessage\n                            });\n                        }\n                        \n                        console.log(`Level up! ${playerName} reached level ${currentLevel}, rewards: ${rewardsGranted.join(', ')}`);\n                    }\n                }\n                \n                console.log(`Playtime XP: ${playerName} gained ${finalXP} XP (${multiplier}x multiplier). Total XP: ${newXP}, Level: ${currentLevel}`);\n                \n            } catch (playerError) {\n                console.error(`Error processing player ${gamePlayer.name}:`, playerError);\n            }\n        }\n        \n        console.log('Active player XP processing completed');\n        \n    } catch (error) {\n        console.error('Error in active player XP cronjob:', error);\n    }\n}\n\nawait main();"
        }
      ],
      "functions": [
        {
          "name": "grantRewards",
          "description": "",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { playerId, gameServerId, levelData, module, playerName } = data;\n    \n    try {\n        if (!levelData || !levelData.level) {\n            console.log('No level data provided for rewards');\n            return { success: false, message: 'No level data' };\n        }\n        \n        const config = module.userConfig;\n        const messages = config.messages || {};\n        let rewardsGranted = [];\n        \n        // Grant currency reward\n        if (levelData.currencyReward && levelData.currencyReward > 0) {\n            await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(\n                gameServerId, \n                playerId, \n                { currency: levelData.currencyReward }\n            );\n            rewardsGranted.push(`${levelData.currencyReward} currency`);\n            console.log(`Granted ${levelData.currencyReward} currency to player ${playerId} for level ${levelData.level}`);\n        }\n        \n        // Grant item rewards\n        if (levelData.itemRewards && levelData.itemRewards.length > 0) {\n            for (const itemReward of levelData.itemRewards) {\n                try {\n                    await takaro.gameserver.gameServerControllerGiveItem(gameServerId, playerId, {\n                        name: itemReward.itemCode,\n                        amount: itemReward.amount,\n                        quality: itemReward.quality || ''\n                    });\n                    rewardsGranted.push(`${itemReward.amount}x ${itemReward.itemCode}`);\n                    console.log(`Granted ${itemReward.amount}x ${itemReward.itemCode} to player ${playerId}`);\n                } catch (itemError) {\n                    console.error(`Failed to grant item ${itemReward.itemCode}:`, itemError);\n                }\n            }\n        }\n        \n        // Mark rewards as claimed\n        const claimKey = `claimedRewards_${levelData.level}`;\n        await takaro.variable.variableControllerCreate({\n            key: claimKey,\n            value: new Date().toISOString(),\n            gameServerId,\n            playerId,\n            moduleId: module.moduleId\n        });\n        \n        // Send level up message\n        const levelUpMessage = messages.levelUpMessage || '🎉 {playerName} reached Level {level} - {levelName}!';\n        const formattedMessage = levelUpMessage\n            .replace('{playerName}', playerName || 'Player')\n            .replace('{level}', levelData.level.toString())\n            .replace('{levelName}', levelData.name || `Level ${levelData.level}`);\n        \n        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n            message: formattedMessage\n        });\n        \n        if (rewardsGranted.length > 0) {\n            const rewardsMessage = `Rewards: ${rewardsGranted.join(', ')}`;\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: rewardsMessage\n            });\n        }\n        \n        console.log(`Level ${levelData.level} rewards granted to ${playerName}: ${rewardsGranted.join(', ')}`);\n        \n        return {\n            success: true,\n            rewardsGranted,\n            message: formattedMessage\n        };\n        \n    } catch (error) {\n        console.error('Error granting rewards:', error);\n        return {\n            success: false,\n            error: error.message,\n            rewardsGranted: []\n        };\n    }\n}\n\nawait main();"
        },
        {
          "name": "calculateXPWithMultiplier",
          "description": "",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { playerId, gameServerId, baseXP, moduleId } = data;\n    \n    try {\n        // Get player's roles and permissions\n        const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, playerId);\n        \n        let multiplier = 1;\n        \n        // Check for XP_MULTIPLIER permission\n        if (playerInfo.data.data.roles && playerInfo.data.data.roles.length > 0) {\n            for (const roleAssignment of playerInfo.data.data.roles) {\n                if (roleAssignment.role && roleAssignment.role.permissions) {\n                    for (const perm of roleAssignment.role.permissions) {\n                        if (perm.permission && perm.permission.permission === 'XP_MULTIPLIER' && perm.permission.moduleVersionId === moduleId) {\n                            // Use the count as multiplier, default to 1 if count is 0\n                            const count = perm.count || 0;\n                            if (count > multiplier) {\n                                multiplier = count;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        \n        const finalXP = Math.floor(baseXP * multiplier);\n        \n        console.log(`Base XP: ${baseXP}, Multiplier: ${multiplier}x, Final XP: ${finalXP}`);\n        \n        return {\n            baseXP,\n            multiplier,\n            finalXP\n        };\n    } catch (error) {\n        console.error('Error calculating XP with multiplier:', error);\n        return {\n            baseXP,\n            multiplier: 1,\n            finalXP: baseXP\n        };\n    }\n}\n\nawait main();"
        },
        {
          "name": "checkLevelUp",
          "description": "",
          "function": "import { data, takaro } from '@takaro/helpers';\n\nasync function main() {\n    const { playerId, gameServerId, currentXP, module } = data;\n    \n    try {\n        const config = module.userConfig;\n        const levels = config.levels || [];\n        \n        // Sort levels by XP requirement\n        const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);\n        \n        // Find current level\n        let currentLevel = 0;\n        let currentLevelData = null;\n        let nextLevelData = null;\n        \n        for (let i = 0; i < sortedLevels.length; i++) {\n            if (currentXP >= sortedLevels[i].xpRequired) {\n                currentLevel = sortedLevels[i].level;\n                currentLevelData = sortedLevels[i];\n            } else {\n                nextLevelData = sortedLevels[i];\n                break;\n            }\n        }\n        \n        // Get stored level from variables\n        const levelVarSearch = await takaro.variable.variableControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                playerId: [playerId],\n                moduleId: [module.moduleId],\n                key: ['playerLevel']\n            }\n        });\n        \n        const storedLevel = levelVarSearch.data.data.length > 0 \n            ? parseInt(levelVarSearch.data.data[0].value) \n            : 0;\n        \n        const leveledUp = currentLevel > storedLevel;\n        \n        // Update stored level if changed\n        if (leveledUp) {\n            if (levelVarSearch.data.data.length > 0) {\n                await takaro.variable.variableControllerUpdate(levelVarSearch.data.data[0].id, {\n                    value: currentLevel.toString()\n                });\n            } else {\n                await takaro.variable.variableControllerCreate({\n                    key: 'playerLevel',\n                    value: currentLevel.toString(),\n                    gameServerId,\n                    playerId,\n                    moduleId: module.moduleId\n                });\n            }\n        }\n        \n        console.log(`Player level check: Current XP: ${currentXP}, Level: ${currentLevel}, Leveled up: ${leveledUp}`);\n        \n        return {\n            currentLevel,\n            currentLevelData,\n            nextLevelData,\n            previousLevel: storedLevel,\n            leveledUp,\n            xpToNextLevel: nextLevelData ? nextLevelData.xpRequired - currentXP : 0\n        };\n    } catch (error) {\n        console.error('Error checking level up:', error);\n        return {\n            currentLevel: 0,\n            currentLevelData: null,\n            nextLevelData: null,\n            previousLevel: 0,\n            leveledUp: false,\n            xpToNextLevel: 0\n        };\n    }\n}\n\nawait main();"
        }
      ],
      "permissions": [
        {
          "permission": "XP_MULTIPLIER",
          "friendlyName": "XP Multiplier",
          "description": "Multiplies XP gains by the permission count value (e.g., count=2 for 2x XP)",
          "canHaveCount": true
        },
        {
          "permission": "PROGRESSION_ADMIN",
          "friendlyName": "Progression Admin",
          "description": "Allows resetting player progression data and managing the progression system",
          "canHaveCount": false
        }
      ]
    }
  ]
}