AFKChecker
- community
- anti-cheat
- by limon
- Takaro main
- all
checks for players if they are afk and kicks them
Configuration 6
Settings you fill in when installing the module on a server.
| Setting | Type | Default | Description |
|---|---|---|---|
minutesBeforeKick required Minutes Between Checks | number | 5 | Time in minutes between each AFK check. This should match your cronjob schedule (for example, if your cronjob runs every 5 minutes, set this to 5). Total AFK time before kick will be (Minutes Between Checks × Maximum AFK Checks). |
maxAfkChecks required Maximum AFK Checks | number | 3 | Number of consecutive AFK checks before a player is kicked. Total AFK time will be (Minutes Between Checks × Maximum AFK Checks). For example, with 5 minutes between checks and 3 max checks, players will be kicked after 15 minutes of being AFK. |
kickMessage required Kick Message | string | "You have been kicked for being AFK for {minutesAfk} minutes" | Message shown to the player when they are kicked for being AFK. You can use {minutesAfk} and {minutesUntilKick} placeholders. |
sendWarning Send Warning | boolean | true | Whether to send a warning to players before kicking them for being AFK |
warningMessage required Warning Message | string | "Warning: You have been AFK for {minutesAfk} minutes. You will be kicked in {minutesUntilKick} minutes unless you move!" | Message sent to warn players before they are kicked for being AFK. You can use {minutesAfk} and {minutesUntilKick} placeholders. |
globalAnnouncement Global Announcement | boolean | false | Whether to announce AFK warnings and kicks to all players on the server |
Raw config schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"maxAfkChecks",
"minutesBeforeKick",
"kickMessage",
"warningMessage"
],
"additionalProperties": false,
"properties": {
"minutesBeforeKick": {
"title": "Minutes Between Checks",
"description": "Time in minutes between each AFK check. This should match your cronjob schedule (for example, if your cronjob runs every 5 minutes, set this to 5). Total AFK time before kick will be (Minutes Between Checks × Maximum AFK Checks).",
"type": "number",
"default": 5,
"minimum": 1
},
"maxAfkChecks": {
"title": "Maximum AFK Checks",
"description": "Number of consecutive AFK checks before a player is kicked. Total AFK time will be (Minutes Between Checks × Maximum AFK Checks). For example, with 5 minutes between checks and 3 max checks, players will be kicked after 15 minutes of being AFK.",
"default": 3,
"type": "number",
"minimum": 1
},
"kickMessage": {
"title": "Kick Message",
"description": "Message shown to the player when they are kicked for being AFK. You can use {minutesAfk} and {minutesUntilKick} placeholders.",
"default": "You have been kicked for being AFK for {minutesAfk} minutes",
"type": "string"
},
"sendWarning": {
"title": "Send Warning",
"description": "Whether to send a warning to players before kicking them for being AFK",
"default": true,
"type": "boolean"
},
"warningMessage": {
"title": "Warning Message",
"description": "Message sent to warn players before they are kicked for being AFK. You can use {minutesAfk} and {minutesUntilKick} placeholders.",
"default": "Warning: You have been AFK for {minutesAfk} minutes. You will be kicked in {minutesUntilKick} minutes unless you move!",
"type": "string"
},
"globalAnnouncement": {
"title": "Global Announcement",
"description": "Whether to announce AFK warnings and kicks to all players on the server",
"default": false,
"type": "boolean"
}
}
} Raw UI schema
{} Cron jobs 1
Work the module runs on a schedule.
-
afkChecker
Cron job source
import { takaro, data, checkPermission } from '@takaro/helpers'; async function main() { const { gameServerId, module: mod } = data; // Get configuration values directly from userConfig const maxAfkChecks = mod.userConfig.maxAfkChecks; const kickMessage = mod.userConfig.kickMessage; const sendWarning = mod.userConfig.sendWarning; const warningMessage = mod.userConfig.warningMessage; const globalAnnouncement = mod.userConfig.globalAnnouncement; const minutesBetweenChecks = mod.userConfig.minutesBeforeKick; // Add a small position tolerance to avoid false AFK positives const positionTolerance = 1.0; // Allow 1 unit of movement without resetting AFK // First, let's clean up variables for offline players await cleanupOfflinePlayerVariables(gameServerId, mod.moduleId); // Get online players through PlayerOnGameServer search const playersResponse = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({ filters: { gameServerId: [gameServerId], online: [true] } }); // If no players online, exit early if (playersResponse.data.meta.total === 0) { return; } // Process each online player for (const playerData of playersResponse.data.data) { // Get the player's POG data to check permissions const pogResponse = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, playerData.player.id); const pog = pogResponse.data.data; // Check if player has immunity let hasImmunity = false; try { // Checking if the player has the permission if (pog) { hasImmunity = checkPermission(pog, 'AFK_IMMUNITY'); } } catch (e) { // If there's an error checking permissions, assume no immunity hasImmunity = false; } // Skip players with AFK immunity if (hasImmunity) { continue; } // Look for the player's last position const lastPositionVar = await takaro.variable.variableControllerSearch({ filters: { key: ['afk_last_position'], playerId: [playerData.player.id], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }); // Get current position from playerData const currentPosition = { x: playerData.positionX, y: playerData.positionY, z: playerData.positionZ }; if (lastPositionVar.data.data.length === 0) { // First time seeing this player, store their position await takaro.variable.variableControllerCreate({ key: 'afk_last_position', value: JSON.stringify(currentPosition), playerId: playerData.player.id, gameServerId: gameServerId, moduleId: mod.moduleId }); // Also initialize their AFK counter await takaro.variable.variableControllerCreate({ key: 'afk_check_count', value: '0', playerId: playerData.player.id, gameServerId: gameServerId, moduleId: mod.moduleId }); continue; } // Parse the last saved position const lastPosition = JSON.parse(lastPositionVar.data.data[0].value); // Get the AFK counter const afkCountVar = await takaro.variable.variableControllerSearch({ filters: { key: ['afk_check_count'], playerId: [playerData.player.id], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }); let afkCount = 0; if (afkCountVar.data.data.length > 0) { afkCount = parseInt(afkCountVar.data.data[0].value); } // Calculate distance moved const distanceX = Math.abs(currentPosition.x - lastPosition.x); const distanceY = Math.abs(currentPosition.y - lastPosition.y); const distanceZ = Math.abs(currentPosition.z - lastPosition.z); // Consider player moved if they moved more than the tolerance in any direction const hasPlayerMoved = ( distanceX > positionTolerance || distanceY > positionTolerance || distanceZ > positionTolerance ); if (hasPlayerMoved) { // Player has moved, reset AFK counter if (afkCountVar.data.data.length > 0) { await takaro.variable.variableControllerUpdate(afkCountVar.data.data[0].id, { value: '0' }); } // Update last position await takaro.variable.variableControllerUpdate(lastPositionVar.data.data[0].id, { value: JSON.stringify(currentPosition) }); } else { // Player hasn't moved, increment AFK counter afkCount++; await takaro.variable.variableControllerUpdate(afkCountVar.data.data[0].id, { value: afkCount.toString() }); // Calculate dynamic message values const checksLeft = maxAfkChecks - afkCount; const minutesAfk = afkCount * minutesBetweenChecks; const minutesUntilKick = checksLeft * minutesBetweenChecks; // Create dynamic messages with placeholders replaced const personalWarningMsg = warningMessage .replace('{warningsLeft}', checksLeft) .replace('{minutesAfk}', minutesAfk) .replace('{minutesUntilKick}', minutesUntilKick); const personalKickMsg = kickMessage .replace('{warningsLeft}', checksLeft) .replace('{minutesAfk}', minutesAfk) .replace('{minutesUntilKick}', minutesUntilKick); // Send warning if configured and there are checks left before max if (sendWarning && checksLeft > 0) { // Send personal warning message to the player await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: personalWarningMsg, opts: { recipient: { gameId: pog.gameId, } } }); // Send global announcement if configured if (globalAnnouncement) { await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `${playerData.player.name} has been AFK for ${minutesAfk} minutes and will be kicked in ${minutesUntilKick} minutes unless they move!` }); } } // If AFK for too long, kick the player if (afkCount >= maxAfkChecks) { // Using the execute command approach which is more reliable await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, { command: `kick "${playerData.player.name}" "${personalKickMsg}"` }); // Send global announcement if configured if (globalAnnouncement) { await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message: `${playerData.player.name} has been kicked for being AFK for ${minutesAfk} minutes.` }); } // Reset counter after kicking await takaro.variable.variableControllerUpdate(afkCountVar.data.data[0].id, { value: '0' }); } // Update last position even if they haven't moved significantly await takaro.variable.variableControllerUpdate(lastPositionVar.data.data[0].id, { value: JSON.stringify(currentPosition) }); } } } // Helper function to clean up variables for offline players async function cleanupOfflinePlayerVariables(gameServerId, moduleId) { // Get all variables for this module and gameserver const allVariables = await takaro.variable.variableControllerSearch({ filters: { gameServerId: [gameServerId], moduleId: [moduleId] } }); if (allVariables.data.data.length === 0) { return; } // Group variables by player ID const variablesByPlayer = {}; for (const variable of allVariables.data.data) { if (variable.playerId) { if (!variablesByPlayer[variable.playerId]) { variablesByPlayer[variable.playerId] = []; } variablesByPlayer[variable.playerId].push(variable); } } // Get list of online player IDs for fast lookup const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({ filters: { gameServerId: [gameServerId], online: [true] } }); const onlinePlayerIds = new Set(onlinePlayers.data.data.map(pog => pog.player.id)); // Delete variables for offline players for (const playerId in variablesByPlayer) { if (!onlinePlayerIds.has(playerId)) { for (const variable of variablesByPlayer[playerId]) { await takaro.variable.variableControllerDelete(variable.id); } } } } await main();
Permissions 1
Roles you can grant to decide who may use what.
-
AFK Kick Immunity
Players with this permission will not be kicked for being AFK.