playerProgressionTracker

  • community
  • community-management
  • by Limon
  • Takaro main
  • all
Version
View export JSON

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.

Configuration 3

Settings you fill in when installing the module on a server.

SettingTypeDefaultDescription
levels required Progression Levels array Configure levels with XP requirements and rewards
xpSettings required XP Settings object
messages Messages object
Raw config schema
{
  "$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"
  ]
}
Raw UI schema
{
  "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 3

Chat commands players trigger in game.

  • progression

    Shows your current level, XP, playtime, and progress to next level

    Command source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, player, pog, module } = data;
        
        try {
            if (!player) {
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: 'Error: Player information not available',
                    opts: { recipient: { gameId: pog.gameId } }
                });
                return;
            }
            
            const config = module.userConfig;
            const levels = config.levels || [];
            const messages = config.messages || {};
            
            // Get player variables
            const variablesSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId]
                }
            });
            
            let playerXP = 0;
            let playerLevel = 0;
            let totalPlaytimeMinutes = 0;
            let killStats = {};
            
            // Parse variables
            for (const variable of variablesSearch.data.data) {
                switch (variable.key) {
                    case 'playerXP':
                        playerXP = parseInt(variable.value) || 0;
                        break;
                    case 'playerLevel':
                        playerLevel = parseInt(variable.value) || 0;
                        break;
                    case 'totalPlaytimeMinutes':
                        totalPlaytimeMinutes = parseInt(variable.value) || 0;
                        break;
                    default:
                        if (variable.key.startsWith('totalKills_')) {
                            const entityName = variable.key.replace('totalKills_', '');
                            killStats[entityName] = parseInt(variable.value) || 0;
                        }
                        break;
                }
            }
            
            // Calculate current level inline (same logic as checkLevelUp function)
            const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);
            
            let currentLevel = 0;
            let currentLevelData = null;
            let nextLevelData = null;
            
            for (let i = 0; i < sortedLevels.length; i++) {
                if (playerXP >= sortedLevels[i].xpRequired) {
                    currentLevel = sortedLevels[i].level;
                    currentLevelData = sortedLevels[i];
                } else {
                    nextLevelData = sortedLevels[i];
                    break;
                }
            }
            
            const xpToNextLevel = nextLevelData ? nextLevelData.xpRequired - playerXP : 0;
            
            // Format playtime
            const hours = Math.floor(totalPlaytimeMinutes / 60);
            const minutes = totalPlaytimeMinutes % 60;
            const playtimeText = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
            
            // Build progression message
            let progressMessage = `=== ${player.name}'s Progression ===\\n`;
            progressMessage += `Level: ${currentLevel}`;
            
            if (currentLevelData && currentLevelData.name) {
                progressMessage += ` - ${currentLevelData.name}`;
            }
            
            progressMessage += `\\nXP: ${playerXP}`;
            
            if (nextLevelData) {
                progressMessage += ` / ${nextLevelData.xpRequired}`;
                progressMessage += `\\nNext Level: ${xpToNextLevel} XP needed`;
                if (nextLevelData.name) {
                    progressMessage += ` (${nextLevelData.name})`;
                }
            } else {
                progressMessage += `\\n🏆 Max level reached!`;
            }
            
            progressMessage += `\\nPlaytime: ${playtimeText}`;
            
            // Add kill stats
            if (Object.keys(killStats).length > 0) {
                progressMessage += `\\n\\nKills:`;
                const sortedKills = Object.entries(killStats)
                    .sort(([,a], [,b]) => b - a)
                    .slice(0, 5); // Top 5
                
                for (const [entity, count] of sortedKills) {
                    progressMessage += `\\n  ${entity}: ${count}`;
                }
            }
            
            // Show next level rewards
            if (nextLevelData) {
                progressMessage += `\\n\\nNext Level Rewards:`;
                if (nextLevelData.currencyReward > 0) {
                    progressMessage += `\\n  💰 ${nextLevelData.currencyReward} currency`;
                }
                if (nextLevelData.itemRewards && nextLevelData.itemRewards.length > 0) {
                    for (const item of nextLevelData.itemRewards) {
                        progressMessage += `\\n  📦 ${item.amount}x ${item.itemCode}`;
                    }
                }
            }
            
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: progressMessage,
                opts: { recipient: { gameId: player.gameId } }
            });
            
            console.log(`Progression stats shown to ${player.name}: Level ${currentLevel}, XP ${playerXP}`);
            
        } catch (error) {
            console.error('Error in progression command:', error);
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: 'Error retrieving progression data. Please try again.',
                opts: { recipient: { gameId: pog.gameId } }
            });
        }
    }
    
    await main();
  • resetprogression

    Resets progression data for a player or all players (Admin only)

    ArgumentTypeDefaultHelp
    target string Player name or 'all' to reset everyone
    Command source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, player, pog, module, arguments: args } = data;
        
        try {
            if (!player) {
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: 'Error: Player information not available',
                    opts: { recipient: { gameId: pog.gameId } }
                });
                return;
            }
            
            const targetPlayer = args.target || '';
            
            if (targetPlayer.toLowerCase() === 'all') {
                // Reset all players on this server
                await resetAllPlayers(gameServerId, module.moduleId, player.name);
            } else if (targetPlayer.trim() !== '') {
                // Reset specific player
                await resetSpecificPlayer(gameServerId, module.moduleId, targetPlayer, player.name);
            } else {
                // No argument provided, show help
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: 'Usage: @resetprogression <playerName> or @resetprogression all\\nResets progression data for specified player or all players.',
                    opts: { recipient: { gameId: player.gameId } }
                });
                return;
            }
            
        } catch (error) {
            console.error('Error in reset progression command:', error);
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: 'Error resetting progression data. Please try again.',
                opts: { recipient: { gameId: pog.gameId } }
            });
        }
    }
    
    async function resetAllPlayers(gameServerId, moduleId, adminName) {
        try {
            // Find all variables for this module on this server
            const allVariables = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    moduleId: [moduleId]
                },
                limit: 1000 // Get many variables
            });
            
            const progressionKeys = ['playerXP', 'playerLevel', 'totalPlaytimeMinutes'];
            let deletedCount = 0;
            let playerCount = 0;
            const processedPlayers = new Set();
            
            // Delete progression variables for all players
            for (const variable of allVariables.data.data) {
                const isProgressionVar = progressionKeys.includes(variable.key) || 
                                       variable.key.startsWith('totalKills_') || 
                                       variable.key.startsWith('claimedRewards_');
                
                if (isProgressionVar) {
                    await takaro.variable.variableControllerDelete(variable.id);
                    deletedCount++;
                    
                    if (!processedPlayers.has(variable.playerId)) {
                        processedPlayers.add(variable.playerId);
                        playerCount++;
                    }
                }
            }
            
            // Send confirmation message
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                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!`
            });
            
            console.log(`Admin ${adminName} reset progression for ${playerCount} players (${deletedCount} variables deleted)`);
            
        } catch (error) {
            console.error('Error resetting all players:', error);
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: 'Error resetting all player data. Please try again.'
            });
        }
    }
    
    async function resetSpecificPlayer(gameServerId, moduleId, targetPlayerName, adminName) {
        try {
            // Find the target player by name
            const playerSearch = await takaro.player.playerControllerSearch({
                search: { name: [targetPlayerName] },
                limit: 10
            });
            
            if (!playerSearch.data.data || playerSearch.data.data.length === 0) {
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: `❌ Player "${targetPlayerName}" not found. Make sure the name is spelled correctly.`
                });
                return;
            }
            
            // Find exact match or closest match
            let targetPlayer = null;
            for (const player of playerSearch.data.data) {
                if (player.name.toLowerCase() === targetPlayerName.toLowerCase()) {
                    targetPlayer = player;
                    break;
                }
            }
            
            if (!targetPlayer) {
                // Use first result if no exact match
                targetPlayer = playerSearch.data.data[0];
            }
            
            // Find all variables for this specific player
            const playerVariables = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [targetPlayer.id],
                    moduleId: [moduleId]
                },
                limit: 100
            });
            
            const progressionKeys = ['playerXP', 'playerLevel', 'totalPlaytimeMinutes'];
            let deletedCount = 0;
            
            // Delete progression variables for this player
            for (const variable of playerVariables.data.data) {
                const isProgressionVar = progressionKeys.includes(variable.key) || 
                                       variable.key.startsWith('totalKills_') || 
                                       variable.key.startsWith('claimedRewards_');
                
                if (isProgressionVar) {
                    await takaro.variable.variableControllerDelete(variable.id);
                    deletedCount++;
                }
            }
            
            // Send confirmation message
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                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!`
            });
            
            console.log(`Admin ${adminName} reset progression for ${targetPlayer.name} (${deletedCount} variables deleted)`);
            
        } catch (error) {
            console.error('Error resetting specific player:', error);
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: `Error resetting data for "${targetPlayerName}". Please try again.`
            });
        }
    }
    
    await main();
  • claim

    Claims any unclaimed level rewards you have earned

    Command source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, player, pog, module } = data;
        
        try {
            if (!player) {
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: 'Error: Player information not available',
                    opts: { recipient: { gameId: pog.gameId } }
                });
                return;
            }
            
            const config = module.userConfig;
            const levels = config.levels || [];
            const messages = config.messages || {};
            
            // Get player XP
            const xpVarSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId],
                    key: ['playerXP']
                }
            });
            
            let playerXP = 0;
            if (xpVarSearch.data.data.length > 0) {
                playerXP = parseInt(xpVarSearch.data.data[0].value) || 0;
            }
            
            // Get level info
            const levelResult = await takaro.function.functionControllerTrigger('6256101e-831c-4a82-b93b-54978ed1f29e', {
                playerId: player.id,
                gameServerId,
                currentXP: playerXP,
                module
            });
            
            const currentLevel = levelResult.data.data.currentLevel;
            
            if (currentLevel === 0) {
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: 'You haven\'t reached any levels yet! Keep playing to gain XP.',
                    opts: { recipient: { gameId: player.gameId } }
                });
                return;
            }
            
            // Get all claimed rewards
            const claimedVarsSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId]
                }
            });
            
            const claimedLevels = new Set();
            for (const variable of claimedVarsSearch.data.data) {
                if (variable.key.startsWith('claimedRewards_')) {
                    const level = parseInt(variable.key.replace('claimedRewards_', ''));
                    claimedLevels.add(level);
                }
            }
            
            // Sort levels by level number
            const sortedLevels = levels.sort((a, b) => a.level - b.level);
            
            // Find unclaimed levels
            const unclaimedLevels = [];
            for (const levelData of sortedLevels) {
                if (levelData.level <= currentLevel && !claimedLevels.has(levelData.level)) {
                    unclaimedLevels.push(levelData);
                }
            }
            
            if (unclaimedLevels.length === 0) {
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: 'No unclaimed rewards available. All your level rewards have been claimed!',
                    opts: { recipient: { gameId: player.gameId } }
                });
                return;
            }
            
            // Claim all unclaimed rewards
            let totalCurrency = 0;
            let allItems = [];
            
            for (const levelData of unclaimedLevels) {
                try {
                    // Grant rewards
                    const rewardResult = await takaro.function.functionControllerTrigger('5fcae3b7-f95c-4e43-bdd9-ccb32d570d24', {
                        playerId: player.id,
                        gameServerId,
                        levelData,
                        module,
                        playerName: player.name
                    });
                    
                    // Track rewards for summary
                    if (levelData.currencyReward > 0) {
                        totalCurrency += levelData.currencyReward;
                    }
                    
                    if (levelData.itemRewards && levelData.itemRewards.length > 0) {
                        allItems.push(...levelData.itemRewards);
                    }
                    
                    console.log(`Claimed level ${levelData.level} rewards for ${player.name}`);
                    
                } catch (rewardError) {
                    console.error(`Error claiming level ${levelData.level} rewards:`, rewardError);
                }
            }
            
            // Send summary message
            let summaryMessage = `✅ Claimed rewards for ${unclaimedLevels.length} levels!`;
            
            if (totalCurrency > 0) {
                summaryMessage += `\n💰 Total currency: ${totalCurrency}`;
            }
            
            if (allItems.length > 0) {
                const itemSummary = {};
                for (const item of allItems) {
                    const key = `${item.itemCode}${item.quality ? ` (${item.quality})` : ''}`;
                    itemSummary[key] = (itemSummary[key] || 0) + item.amount;
                }
                
                summaryMessage += `\n📦 Items received:`;
                for (const [itemDesc, count] of Object.entries(itemSummary)) {
                    summaryMessage += `\n  ${count}x ${itemDesc}`;
                }
            }
            
            const levelsText = unclaimedLevels.map(l => l.level).join(', ');
            summaryMessage += `\n\nLevels claimed: ${levelsText}`;
            
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: summaryMessage,
                opts: { recipient: { gameId: player.gameId } }
            });
            
            console.log(`${player.name} claimed rewards for levels: ${levelsText}`);
            
        } catch (error) {
            console.error('Error in claim command:', error);
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: 'Error claiming rewards. Please try again.',
                opts: { recipient: { gameId: pog.gameId } }
            });
        }
    }
    
    await main();

Hooks 2

Code that runs in reaction to a game or Takaro event.

  • Entity Kill XP

    Awards XP when player kills entities

    Hook source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, eventData, player, module } = data;
        
        try {
            if (!player) {
                console.log('No player data in entity kill event');
                return;
            }
            
            const config = module.userConfig;
            const xpSettings = config.xpSettings || {};
            const baseKillXP = xpSettings.baseKillXP || 10;
            const entityXPOverrides = xpSettings.entityXPOverrides || {};
            
            // Parse event data to get entity information
            let entityName = 'unknown';
            let baseXP = baseKillXP;
            
            if (eventData && eventData.entity) {
                entityName = eventData.entity.toLowerCase();
                
                // Check for entity-specific XP override
                if (entityXPOverrides[entityName] !== undefined) {
                    baseXP = entityXPOverrides[entityName];
                }
            }
            
            console.log(`Player ${player.name} killed ${entityName} - Base XP: ${baseXP}`);
            
            // Inline XP multiplier calculation (from calculateXPWithMultiplier function)
            let multiplier = 1;
            try {
                const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id);
                
                if (playerInfo.data.data.roles && playerInfo.data.data.roles.length > 0) {
                    for (const roleAssignment of playerInfo.data.data.roles) {
                        if (roleAssignment.role && roleAssignment.role.permissions) {
                            for (const perm of roleAssignment.role.permissions) {
                                if (perm.permission && perm.permission.permission === 'XP_MULTIPLIER' && perm.permission.moduleVersionId === module.versionId) {
                                    const count = perm.count || 0;
                                    if (count > multiplier) {
                                        multiplier = count;
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (permError) {
                console.log(`Could not check permissions for ${player.name}, using 1x multiplier`);
            }
            
            const finalXP = Math.floor(baseXP * multiplier);
            
            // Get current player XP
            const xpVarSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId],
                    key: ['playerXP']
                }
            });
            
            let currentXP = 0;
            if (xpVarSearch.data.data.length > 0) {
                currentXP = parseInt(xpVarSearch.data.data[0].value) || 0;
            }
            
            const newXP = currentXP + finalXP;
            
            // Update XP variable
            if (xpVarSearch.data.data.length > 0) {
                await takaro.variable.variableControllerUpdate(xpVarSearch.data.data[0].id, {
                    value: newXP.toString()
                });
            } else {
                await takaro.variable.variableControllerCreate({
                    key: 'playerXP',
                    value: newXP.toString(),
                    gameServerId,
                    playerId: player.id,
                    moduleId: module.moduleId
                });
            }
            
            // Update kill counter for this entity type
            const killKey = `totalKills_${entityName}`;
            const killVarSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId],
                    key: [killKey]
                }
            });
            
            let currentKills = 0;
            if (killVarSearch.data.data.length > 0) {
                currentKills = parseInt(killVarSearch.data.data[0].value) || 0;
            }
            
            const newKills = currentKills + 1;
            
            if (killVarSearch.data.data.length > 0) {
                await takaro.variable.variableControllerUpdate(killVarSearch.data.data[0].id, {
                    value: newKills.toString()
                });
            } else {
                await takaro.variable.variableControllerCreate({
                    key: killKey,
                    value: '1',
                    gameServerId,
                    playerId: player.id,
                    moduleId: module.moduleId
                });
            }
            
            // Inline level checking (from checkLevelUp function)
            const levels = config.levels || [];
            const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);
            
            let currentLevel = 0;
            let currentLevelData = null;
            let nextLevelData = null;
            
            for (let i = 0; i < sortedLevels.length; i++) {
                if (newXP >= sortedLevels[i].xpRequired) {
                    currentLevel = sortedLevels[i].level;
                    currentLevelData = sortedLevels[i];
                } else {
                    nextLevelData = sortedLevels[i];
                    break;
                }
            }
            
            // Get stored level from variables
            const levelVarSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId],
                    key: ['playerLevel']
                }
            });
            
            const storedLevel = levelVarSearch.data.data.length > 0 
                ? parseInt(levelVarSearch.data.data[0].value) 
                : 0;
            
            const leveledUp = currentLevel > storedLevel;
            
            // Update stored level if changed
            if (leveledUp) {
                if (levelVarSearch.data.data.length > 0) {
                    await takaro.variable.variableControllerUpdate(levelVarSearch.data.data[0].id, {
                        value: currentLevel.toString()
                    });
                } else {
                    await takaro.variable.variableControllerCreate({
                        key: 'playerLevel',
                        value: currentLevel.toString(),
                        gameServerId,
                        playerId: player.id,
                        moduleId: module.moduleId
                    });
                }
                
                // Inline reward granting (from grantRewards function)
                if (currentLevelData) {
                    const messages = config.messages || {};
                    let rewardsGranted = [];
                    
                    // Grant currency reward
                    if (currentLevelData.currencyReward && currentLevelData.currencyReward > 0) {
                        await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(
                            gameServerId, 
                            player.id, 
                            { currency: currentLevelData.currencyReward }
                        );
                        rewardsGranted.push(`${currentLevelData.currencyReward} currency`);
                    }
                    
                    // Grant item rewards
                    if (currentLevelData.itemRewards && currentLevelData.itemRewards.length > 0) {
                        for (const itemReward of currentLevelData.itemRewards) {
                            try {
                                await takaro.gameserver.gameServerControllerGiveItem(gameServerId, player.id, {
                                    name: itemReward.itemCode,
                                    amount: itemReward.amount,
                                    quality: itemReward.quality || ''
                                });
                                rewardsGranted.push(`${itemReward.amount}x ${itemReward.itemCode}`);
                            } catch (itemError) {
                                console.error(`Failed to grant item ${itemReward.itemCode}:`, itemError);
                            }
                        }
                    }
                    
                    // Mark rewards as claimed
                    const claimKey = `claimedRewards_${currentLevelData.level}`;
                    await takaro.variable.variableControllerCreate({
                        key: claimKey,
                        value: new Date().toISOString(),
                        gameServerId,
                        playerId: player.id,
                        moduleId: module.moduleId
                    });
                    
                    // Send level up message
                    const levelUpMessage = messages.levelUpMessage || '🎉 {playerName} reached Level {level} - {levelName}!';
                    const formattedMessage = levelUpMessage
                        .replace('{playerName}', player.name)
                        .replace('{level}', currentLevelData.level.toString())
                        .replace('{levelName}', currentLevelData.name || `Level ${currentLevelData.level}`);
                    
                    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                        message: formattedMessage
                    });
                    
                    if (rewardsGranted.length > 0) {
                        const rewardsMessage = `Rewards: ${rewardsGranted.join(', ')}`;
                        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                            message: rewardsMessage
                        });
                    }
                    
                    console.log(`Level up! ${player.name} reached level ${currentLevel} from kill, rewards: ${rewardsGranted.join(', ')}`);
                }
            }
            
            console.log(`Kill XP awarded: ${player.name} gained ${finalXP} XP (${multiplier}x multiplier) for killing ${entityName}. Total XP: ${newXP}, Level: ${currentLevel}`);
            
        } catch (error) {
            console.error('Error in entity kill XP hook:', error);
        }
    }
    
    await main();
  • Player Connected Playtime

    Tracks player connections for playtime XP calculations

    Hook source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, eventData, player, module } = data;
        
        try {
            if (!player) {
                console.log('No player data available');
                return;
            }
            
            console.log(`Player ${player.name} connected - updating playtime tracking`);
            
            // Store connection time for playtime XP calculation
            await takaro.variable.variableControllerCreate({
                key: 'lastConnectionTime',
                value: new Date().toISOString(),
                gameServerId,
                playerId: player.id,
                moduleId: module.moduleId
            });
            
            // Initialize player XP if it doesn't exist
            const xpVarSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId],
                    key: ['playerXP']
                }
            });
            
            if (xpVarSearch.data.data.length === 0) {
                await takaro.variable.variableControllerCreate({
                    key: 'playerXP',
                    value: '0',
                    gameServerId,
                    playerId: player.id,
                    moduleId: module.moduleId
                });
                
                console.log(`Initialized XP for new player ${player.name}`);
            }
            
            // Initialize player level if it doesn't exist
            const levelVarSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [player.id],
                    moduleId: [module.moduleId],
                    key: ['playerLevel']
                }
            });
            
            if (levelVarSearch.data.data.length === 0) {
                await takaro.variable.variableControllerCreate({
                    key: 'playerLevel',
                    value: '0',
                    gameServerId,
                    playerId: player.id,
                    moduleId: module.moduleId
                });
            }
            
            console.log(`Player connection tracking updated for ${player.name}`);
            
        } catch (error) {
            console.error('Error in player connected hook:', error);
        }
    }
    
    await main();

Cron jobs 1

Work the module runs on a schedule.

  • Active Player XP

    Awards XP to active players based on playtime

    Cron job source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, module } = data;
        
        try {
            const config = module.userConfig;
            const xpSettings = config.xpSettings || {};
            const xpPerHour = xpSettings.xpPerHourPlayed || 100;
            const checkIntervalMinutes = xpSettings.activePlayerMinutes || 5;
            
            // Get all online players on this server
            const playersResult = await takaro.gameserver.gameServerControllerGetPlayers(gameServerId);
            
            if (!playersResult.data.data || playersResult.data.data.length === 0) {
                console.log('No online players found');
                return;
            }
            
            console.log(`Checking active XP for ${playersResult.data.data.length} online players`);
            
            for (const gamePlayer of playersResult.data.data) {
                try {
                    const playerName = gamePlayer.name;
                    
                    // Find Takaro player ID by searching for players with matching identifiers
                    let playerSearchResult;
                    
                    if (gamePlayer.steamId) {
                        playerSearchResult = await takaro.player.playerControllerSearch({
                            filters: {
                                steamId: [gamePlayer.steamId]
                            },
                            limit: 1
                        });
                    } else if (gamePlayer.epicOnlineServicesId) {
                        playerSearchResult = await takaro.player.playerControllerSearch({
                            filters: {
                                epicOnlineServicesId: [gamePlayer.epicOnlineServicesId]
                            },
                            limit: 1
                        });
                    }
                    
                    if (!playerSearchResult || !playerSearchResult.data.data || playerSearchResult.data.data.length === 0) {
                        console.log(`Could not find Takaro player for game player: ${playerName}`);
                        continue;
                    }
                    
                    const playerId = playerSearchResult.data.data[0].id;
                    
                    // Calculate XP for the interval
                    const baseXPForInterval = Math.floor((xpPerHour / 60) * checkIntervalMinutes);
                    
                    if (baseXPForInterval <= 0) {
                        continue;
                    }
                    
                    // Inline XP multiplier calculation (from calculateXPWithMultiplier function)
                    let multiplier = 1;
                    try {
                        const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, playerId);
                        
                        if (playerInfo.data.data.roles && playerInfo.data.data.roles.length > 0) {
                            for (const roleAssignment of playerInfo.data.data.roles) {
                                if (roleAssignment.role && roleAssignment.role.permissions) {
                                    for (const perm of roleAssignment.role.permissions) {
                                        if (perm.permission && perm.permission.permission === 'XP_MULTIPLIER' && perm.permission.moduleVersionId === module.versionId) {
                                            const count = perm.count || 0;
                                            if (count > multiplier) {
                                                multiplier = count;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } catch (permError) {
                        console.log(`Could not check permissions for ${playerName}, using 1x multiplier`);
                    }
                    
                    const finalXP = Math.floor(baseXPForInterval * multiplier);
                    
                    // Get current player XP
                    const xpVarSearch = await takaro.variable.variableControllerSearch({
                        filters: {
                            gameServerId: [gameServerId],
                            playerId: [playerId],
                            moduleId: [module.moduleId],
                            key: ['playerXP']
                        }
                    });
                    
                    let currentXP = 0;
                    if (xpVarSearch.data.data.length > 0) {
                        currentXP = parseInt(xpVarSearch.data.data[0].value) || 0;
                    }
                    
                    const newXP = currentXP + finalXP;
                    
                    // Update XP variable
                    if (xpVarSearch.data.data.length > 0) {
                        await takaro.variable.variableControllerUpdate(xpVarSearch.data.data[0].id, {
                            value: newXP.toString()
                        });
                    } else {
                        await takaro.variable.variableControllerCreate({
                            key: 'playerXP',
                            value: newXP.toString(),
                            gameServerId,
                            playerId,
                            moduleId: module.moduleId
                        });
                    }
                    
                    // Update total playtime tracking
                    const playtimeVarSearch = await takaro.variable.variableControllerSearch({
                        filters: {
                            gameServerId: [gameServerId],
                            playerId: [playerId],
                            moduleId: [module.moduleId],
                            key: ['totalPlaytimeMinutes']
                        }
                    });
                    
                    let totalMinutes = 0;
                    if (playtimeVarSearch.data.data.length > 0) {
                        totalMinutes = parseInt(playtimeVarSearch.data.data[0].value) || 0;
                    }
                    
                    const newTotalMinutes = totalMinutes + checkIntervalMinutes;
                    
                    if (playtimeVarSearch.data.data.length > 0) {
                        await takaro.variable.variableControllerUpdate(playtimeVarSearch.data.data[0].id, {
                            value: newTotalMinutes.toString()
                        });
                    } else {
                        await takaro.variable.variableControllerCreate({
                            key: 'totalPlaytimeMinutes',
                            value: checkIntervalMinutes.toString(),
                            gameServerId,
                            playerId,
                            moduleId: module.moduleId
                        });
                    }
                    
                    // Inline level checking (from checkLevelUp function)
                    const levels = config.levels || [];
                    const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);
                    
                    let currentLevel = 0;
                    let currentLevelData = null;
                    let nextLevelData = null;
                    
                    for (let i = 0; i < sortedLevels.length; i++) {
                        if (newXP >= sortedLevels[i].xpRequired) {
                            currentLevel = sortedLevels[i].level;
                            currentLevelData = sortedLevels[i];
                        } else {
                            nextLevelData = sortedLevels[i];
                            break;
                        }
                    }
                    
                    // Get stored level from variables
                    const levelVarSearch = await takaro.variable.variableControllerSearch({
                        filters: {
                            gameServerId: [gameServerId],
                            playerId: [playerId],
                            moduleId: [module.moduleId],
                            key: ['playerLevel']
                        }
                    });
                    
                    const storedLevel = levelVarSearch.data.data.length > 0 
                        ? parseInt(levelVarSearch.data.data[0].value) 
                        : 0;
                    
                    const leveledUp = currentLevel > storedLevel;
                    
                    // Update stored level if changed
                    if (leveledUp) {
                        if (levelVarSearch.data.data.length > 0) {
                            await takaro.variable.variableControllerUpdate(levelVarSearch.data.data[0].id, {
                                value: currentLevel.toString()
                            });
                        } else {
                            await takaro.variable.variableControllerCreate({
                                key: 'playerLevel',
                                value: currentLevel.toString(),
                                gameServerId,
                                playerId,
                                moduleId: module.moduleId
                            });
                        }
                        
                        // Inline reward granting (from grantRewards function)
                        if (currentLevelData) {
                            const messages = config.messages || {};
                            let rewardsGranted = [];
                            
                            // Grant currency reward
                            if (currentLevelData.currencyReward && currentLevelData.currencyReward > 0) {
                                await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(
                                    gameServerId, 
                                    playerId, 
                                    { currency: currentLevelData.currencyReward }
                                );
                                rewardsGranted.push(`${currentLevelData.currencyReward} currency`);
                            }
                            
                            // Grant item rewards
                            if (currentLevelData.itemRewards && currentLevelData.itemRewards.length > 0) {
                                for (const itemReward of currentLevelData.itemRewards) {
                                    try {
                                        await takaro.gameserver.gameServerControllerGiveItem(gameServerId, playerId, {
                                            name: itemReward.itemCode,
                                            amount: itemReward.amount,
                                            quality: itemReward.quality || ''
                                        });
                                        rewardsGranted.push(`${itemReward.amount}x ${itemReward.itemCode}`);
                                    } catch (itemError) {
                                        console.error(`Failed to grant item ${itemReward.itemCode}:`, itemError);
                                    }
                                }
                            }
                            
                            // Mark rewards as claimed
                            const claimKey = `claimedRewards_${currentLevelData.level}`;
                            await takaro.variable.variableControllerCreate({
                                key: claimKey,
                                value: new Date().toISOString(),
                                gameServerId,
                                playerId,
                                moduleId: module.moduleId
                            });
                            
                            // Send level up message
                            const levelUpMessage = messages.levelUpMessage || '🎉 {playerName} reached Level {level} - {levelName}!';
                            const formattedMessage = levelUpMessage
                                .replace('{playerName}', playerName || 'Player')
                                .replace('{level}', currentLevelData.level.toString())
                                .replace('{levelName}', currentLevelData.name || `Level ${currentLevelData.level}`);
                            
                            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                                message: formattedMessage
                            });
                            
                            if (rewardsGranted.length > 0) {
                                const rewardsMessage = `Rewards: ${rewardsGranted.join(', ')}`;
                                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                                    message: rewardsMessage
                                });
                            }
                            
                            console.log(`Level up! ${playerName} reached level ${currentLevel}, rewards: ${rewardsGranted.join(', ')}`);
                        }
                    }
                    
                    console.log(`Playtime XP: ${playerName} gained ${finalXP} XP (${multiplier}x multiplier). Total XP: ${newXP}, Level: ${currentLevel}`);
                    
                } catch (playerError) {
                    console.error(`Error processing player ${gamePlayer.name}:`, playerError);
                }
            }
            
            console.log('Active player XP processing completed');
            
        } catch (error) {
            console.error('Error in active player XP cronjob:', error);
        }
    }
    
    await main();

Functions 3

Shared helpers the module's commands, hooks and cron jobs import.

  • checkLevelUp

    Checks if player has leveled up and returns new level info

    Function source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { playerId, gameServerId, currentXP, module } = data;
        
        try {
            const config = module.userConfig;
            const levels = config.levels || [];
            
            // Sort levels by XP requirement
            const sortedLevels = levels.sort((a, b) => a.xpRequired - b.xpRequired);
            
            // Find current level
            let currentLevel = 0;
            let currentLevelData = null;
            let nextLevelData = null;
            
            for (let i = 0; i < sortedLevels.length; i++) {
                if (currentXP >= sortedLevels[i].xpRequired) {
                    currentLevel = sortedLevels[i].level;
                    currentLevelData = sortedLevels[i];
                } else {
                    nextLevelData = sortedLevels[i];
                    break;
                }
            }
            
            // Get stored level from variables
            const levelVarSearch = await takaro.variable.variableControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    playerId: [playerId],
                    moduleId: [module.moduleId],
                    key: ['playerLevel']
                }
            });
            
            const storedLevel = levelVarSearch.data.data.length > 0 
                ? parseInt(levelVarSearch.data.data[0].value) 
                : 0;
            
            const leveledUp = currentLevel > storedLevel;
            
            // Update stored level if changed
            if (leveledUp) {
                if (levelVarSearch.data.data.length > 0) {
                    await takaro.variable.variableControllerUpdate(levelVarSearch.data.data[0].id, {
                        value: currentLevel.toString()
                    });
                } else {
                    await takaro.variable.variableControllerCreate({
                        key: 'playerLevel',
                        value: currentLevel.toString(),
                        gameServerId,
                        playerId,
                        moduleId: module.moduleId
                    });
                }
            }
            
            console.log(`Player level check: Current XP: ${currentXP}, Level: ${currentLevel}, Leveled up: ${leveledUp}`);
            
            return {
                currentLevel,
                currentLevelData,
                nextLevelData,
                previousLevel: storedLevel,
                leveledUp,
                xpToNextLevel: nextLevelData ? nextLevelData.xpRequired - currentXP : 0
            };
        } catch (error) {
            console.error('Error checking level up:', error);
            return {
                currentLevel: 0,
                currentLevelData: null,
                nextLevelData: null,
                previousLevel: 0,
                leveledUp: false,
                xpToNextLevel: 0
            };
        }
    }
    
    await main();
  • grantRewards

    Grants currency and item rewards for level progression

    Function source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { playerId, gameServerId, levelData, module, playerName } = data;
        
        try {
            if (!levelData || !levelData.level) {
                console.log('No level data provided for rewards');
                return { success: false, message: 'No level data' };
            }
            
            const config = module.userConfig;
            const messages = config.messages || {};
            let rewardsGranted = [];
            
            // Grant currency reward
            if (levelData.currencyReward && levelData.currencyReward > 0) {
                await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(
                    gameServerId, 
                    playerId, 
                    { currency: levelData.currencyReward }
                );
                rewardsGranted.push(`${levelData.currencyReward} currency`);
                console.log(`Granted ${levelData.currencyReward} currency to player ${playerId} for level ${levelData.level}`);
            }
            
            // Grant item rewards
            if (levelData.itemRewards && levelData.itemRewards.length > 0) {
                for (const itemReward of levelData.itemRewards) {
                    try {
                        await takaro.gameserver.gameServerControllerGiveItem(gameServerId, playerId, {
                            name: itemReward.itemCode,
                            amount: itemReward.amount,
                            quality: itemReward.quality || ''
                        });
                        rewardsGranted.push(`${itemReward.amount}x ${itemReward.itemCode}`);
                        console.log(`Granted ${itemReward.amount}x ${itemReward.itemCode} to player ${playerId}`);
                    } catch (itemError) {
                        console.error(`Failed to grant item ${itemReward.itemCode}:`, itemError);
                    }
                }
            }
            
            // Mark rewards as claimed
            const claimKey = `claimedRewards_${levelData.level}`;
            await takaro.variable.variableControllerCreate({
                key: claimKey,
                value: new Date().toISOString(),
                gameServerId,
                playerId,
                moduleId: module.moduleId
            });
            
            // Send level up message
            const levelUpMessage = messages.levelUpMessage || '🎉 {playerName} reached Level {level} - {levelName}!';
            const formattedMessage = levelUpMessage
                .replace('{playerName}', playerName || 'Player')
                .replace('{level}', levelData.level.toString())
                .replace('{levelName}', levelData.name || `Level ${levelData.level}`);
            
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                message: formattedMessage
            });
            
            if (rewardsGranted.length > 0) {
                const rewardsMessage = `Rewards: ${rewardsGranted.join(', ')}`;
                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
                    message: rewardsMessage
                });
            }
            
            console.log(`Level ${levelData.level} rewards granted to ${playerName}: ${rewardsGranted.join(', ')}`);
            
            return {
                success: true,
                rewardsGranted,
                message: formattedMessage
            };
            
        } catch (error) {
            console.error('Error granting rewards:', error);
            return {
                success: false,
                error: error.message,
                rewardsGranted: []
            };
        }
    }
    
    await main();
  • calculateXPWithMultiplier

    Calculates XP with permission-based multipliers

    Function source
    import { data, takaro } from '@takaro/helpers';
    
    async function main() {
        const { playerId, gameServerId, baseXP, moduleId } = data;
        
        try {
            // Get player's roles and permissions
            const playerInfo = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, playerId);
            
            let multiplier = 1;
            
            // Check for XP_MULTIPLIER permission
            if (playerInfo.data.data.roles && playerInfo.data.data.roles.length > 0) {
                for (const roleAssignment of playerInfo.data.data.roles) {
                    if (roleAssignment.role && roleAssignment.role.permissions) {
                        for (const perm of roleAssignment.role.permissions) {
                            if (perm.permission && perm.permission.permission === 'XP_MULTIPLIER' && perm.permission.moduleVersionId === moduleId) {
                                // Use the count as multiplier, default to 1 if count is 0
                                const count = perm.count || 0;
                                if (count > multiplier) {
                                    multiplier = count;
                                }
                            }
                        }
                    }
                }
            }
            
            const finalXP = Math.floor(baseXP * multiplier);
            
            console.log(`Base XP: ${baseXP}, Multiplier: ${multiplier}x, Final XP: ${finalXP}`);
            
            return {
                baseXP,
                multiplier,
                finalXP
            };
        } catch (error) {
            console.error('Error calculating XP with multiplier:', error);
            return {
                baseXP,
                multiplier: 1,
                finalXP: baseXP
            };
        }
    }
    
    await main();

Permissions 2

Roles you can grant to decide who may use what.

  • XP Multiplier

    Multiplies XP gains by the permission count value (e.g., count=2 for 2x XP)

  • Progression Admin

    Allows resetting player progression data and managing the progression system