playerProgressionTracker
- community
- community-management
- by Limon
- Takaro main
- all
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.
| Setting | Type | Default | Description |
|---|---|---|---|
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)
Argument Type Default Help targetstring 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