{
  "name": "RegionResetRandomiser",
  "author": "trevor",
  "supportedGames": [
    "all"
  ],
  "takaroVersion": "main",
  "versions": [
    {
      "tag": "latest",
      "description": "# Trevor_RegionResetRandomiser: Map Region Reset Manager\n\nThe **Trevor_RegionResetRandomiser** module provides an intelligent solution for 7 Days to Die servers to maintain fresh gameplay experiences through automated region resets. This advanced Takaro module works with CPM to randomly select and reset map regions on a configurable schedule.\n\n## Key Benefits:\n- **Reset Regions**: Keeps the game world fresh with renewed resources in selected areas\n- **Performance Optimization**: Targeted resets help maintain server performance\n- **Balanced Gameplay**: Prevents complete resource depletion while preserving player builds\n- **Selective Regeneration**: Only resets a configurable subset of regions\n- **Administrator Control**: Allows manual triggering via Discord commands\n\n## Features:\n* Configurable number of regions to reset at each interval\n* Automatic daily region reset via cron job scheduling\n* Manual reset capability through Discord commands\n* Permission-based control with authorized user list\n* Custom activation phrase for manual triggering\n* Detailed debug logging to Discord channels\n* Smart region selection algorithm\n* Error handling for various edge cases\n* Unicode support for international player names\n* Timestamp logging for audit trails\n\nRequires Prisma501's CPM mod for 7 Days to Die. Ideal for long-running servers that need region resets without full map resets.",
      "configSchema": "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"required\":[\"resetQuantity\"],\"additionalProperties\":false,\"properties\":{\"resetQuantity\":{\"title\":\"resetQuantity\",\"description\":\"(Required) Specify the number of regions to randomly selected for reset.\\nMust be fewer than the total number of flagged regions.\",\"default\":5,\"type\":\"number\"},\"debugChannel\":{\"title\":\"debugChannel\",\"description\":\"(Optional) Supply a discord channel ID to receive debug activity messages.\",\"default\":\"\",\"type\":\"string\"},\"authorisedPlayer\":{\"title\":\"authorisedPlayer\",\"description\":\"People authorised to manually execute the hook via the discord debugChannel. Separate with commas. e.g.:  Trevor, Barry, John\\n\\nTested OK with unicode like 🆃🆁🅴🆅🅾🆁\\n\\nNot tested with odd symbols like: ☕ Catalysm\",\"type\":\"string\"},\"activationPhrase\":{\"title\":\"activationPhrase\",\"description\":\"To manually trigger this module, the authorised person(s) can type this keyphrase into the Discord debugChannel specified above.  e.g.: reset-regions\",\"default\":\"reset-regions\",\"type\":\"string\"},\"sendDebugMessages\":{\"title\":\"sendDebugMessages\",\"description\":\"If enabled, debug messages will be sent to the debugChannel.\\n\\nThe listening channel for keyphrase triggering is specified separately in the hook, below.\",\"default\":false,\"type\":\"boolean\"}}}",
      "uiSchema": "{}",
      "commands": [],
      "hooks": [
        {
          "name": "randomiseResetRegions",
          "eventType": "discord-message",
          "description": "Hook for discord-message events",
          "regex": null,
          "function": "import { data, takaro } from '@takaro/helpers';\nasync function main() {\n    const { module: mod } = data;\n    const resetQuantity = mod.userConfig.resetQuantity;\n    const debugChannel = mod.userConfig.debugChannel;\n    const authorisedPlayer = mod.userConfig.authorisedPlayer;\n    const activationPhrase = mod.userConfig.activationPhrase;\n    const sendDebugMessages = mod.userConfig.sendDebugMessages;\n    const randomRegionList = [];\n\n    // Validate user-supplied discord channelIDs\n    const validDebugChannel = debugChannel.length < 20 && /\\d{18}/.test(debugChannel)\n    // Helper function(s)\n    function getTimeStamp() {\n        const d = new Date();\n        return `[${d.getFullYear()}-${d.getMonth()}-${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()} UTC]`;\n    }\n    function getRndInteger(min, max) {\n        return Math.floor(Math.random() * (max - min)) + min;\n    }\n\n    if (validDebugChannel) {\n        if (!data.eventData.author.isBot) {\n            if (authorisedPlayer.includes(data.eventData.author.displayName)) {\n                if (data.eventData.msg === activationPhrase) {\n                    // Get list of existing reset regions\n                    const rawRegionList = (await takaro.gameserver.gameServerControllerExecuteCommand(data.gameServerId, { command: `mrr list` })).data.data.rawResult;\n                    const fullRegionList = rawRegionList.split(\"\\r\\n\");\n                    fullRegionList.shift();\n                    fullRegionList.pop();\n\n                    if (validDebugChannel && sendDebugMessages) {\n                        await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} Begin reset region process.` });\n                    }\n                    // Ensure there are some existing regions to work with\n                    if (fullRegionList.length > 0) {\n                        // Ensure there are enough existing regions to work with\n                        if (fullRegionList.length > Number(resetQuantity)) {\n\n                            // Make a random selection of regions to reset\n                            for (let i = 0; i < resetQuantity; i++) {\n                                randomRegionList.push(fullRegionList[getRndInteger(0, Number(fullRegionList.length))]);\n                            }\n                            if (validDebugChannel && sendDebugMessages) {\n                                await takaro.discord.discordControllerSendMessage(debugChannel, { message: ` Prepared ${randomRegionList.length} regions for reset.` });\n                            }\n\n                            // Reset the randomised selection\n                            if (randomRegionList.length > 0) {\n                                for (let i = 0; i < randomRegionList.length; i++) {\n                                    const [a, x, z, b] = randomRegionList[i].split(\".\");\n                                    await takaro.gameserver.gameServerControllerExecuteCommand(data.gameServerId, { command: `rr ${x} ${z} 2` });\n                                }\n                                if (validDebugChannel && sendDebugMessages) {\n                                    await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} ${randomRegionList.length} regions were reset: ${randomRegionList.toString()}` });\n                                }\n\n                            } else {\n                                if (validDebugChannel && sendDebugMessages) {\n                                    await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} Something broke! Randomised selection is empty!` });\n                                }\n                            }\n                        } else {\n                            if (validDebugChannel && sendDebugMessages) {\n                                await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} Can't select ${resetQuantity} regions from a list of ${fullRegionList.length} regions!` });\n                            }\n                        }\n\n                    } else {\n                        if (validDebugChannel && sendDebugMessages) {\n                            await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} No reset regions are currently defined!` });\n                        }\n                    }\n\n                    if (validDebugChannel && sendDebugMessages) {\n                        await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} End reset region process.` });\n                    }\n                }\n            }\n        }\n    }\n}\nawait main();"
        }
      ],
      "cronJobs": [
        {
          "name": "randomiseResetRegions",
          "temporalValue": "0 0 * * *",
          "description": "",
          "function": "import { data, takaro } from '@takaro/helpers';\nasync function main() {\n    const { module: mod } = data;\n    const resetQuantity = mod.userConfig.resetQuantity;\n    const debugChannel = mod.userConfig.debugChannel;\n    const sendDebugMessages = mod.userConfig.sendDebugMessages;\n    const randomRegionList = [];\n\n    // Validate user-supplied discord channelIDs\n    const validDebugChannel = debugChannel.length < 20 && /\\d{18}/.test(debugChannel)\n    // Helper function(s)\n    function getTimeStamp() {\n        const d = new Date();\n        return `[${d.getFullYear()}-${d.getMonth()}-${d.getDate()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()} UTC]`;\n    }\n    function getRndInteger(min, max) {\n        return Math.floor(Math.random() * (max - min)) + min;\n    }\n\n    // Get list of existing reset regions\n    const rawRegionList = (await takaro.gameserver.gameServerControllerExecuteCommand(data.gameServerId, { command: `mrr list` })).data.data.rawResult;\n    const fullRegionList = rawRegionList.split(\"\\r\\n\");\n    fullRegionList.shift();\n    fullRegionList.pop();\n\n    if (validDebugChannel && sendDebugMessages) {\n        await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} Begin reset region process.` });\n    }\n    // Ensure there are some existing regions to work with\n    if (fullRegionList.length > 0) {\n        // Ensure there are enough existing regions to work with\n        if (fullRegionList.length > Number(resetQuantity)) {\n\n            // Make a random selection of regions to reset\n            for (let i = 0; i < resetQuantity; i++) {\n                randomRegionList.push(fullRegionList[getRndInteger(0, Number(fullRegionList.length))]);\n            }\n            if (validDebugChannel && sendDebugMessages) {\n                await takaro.discord.discordControllerSendMessage(debugChannel, { message: ` Prepared ${randomRegionList.length} regions for reset.` });\n            }\n\n            // Reset the randomised selection\n            if (randomRegionList.length > 0) {\n                for (let i = 0; i < randomRegionList.length; i++) {\n                    const [a, x, z, b] = randomRegionList[i].split(\".\");\n                    await takaro.gameserver.gameServerControllerExecuteCommand(data.gameServerId, { command: `rr ${x} ${z} 2` });\n                }\n                if (validDebugChannel && sendDebugMessages) {\n                    await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} ${randomRegionList.length} regions were reset: ${randomRegionList.toString()}` });\n                }\n\n            } else {\n                if (validDebugChannel && sendDebugMessages) {\n                    await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} Something broke! Randomised selection is empty!` });\n                }\n            }\n        } else {\n            if (validDebugChannel && sendDebugMessages) {\n                await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} Can't select ${resetQuantity} regions from a list of ${fullRegionList.length} regions!` });\n            }\n        }\n\n    } else {\n        if (validDebugChannel && sendDebugMessages) {\n            await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} No reset regions are currently defined!` });\n        }\n    }\n\n    if (validDebugChannel && sendDebugMessages) {\n        await takaro.discord.discordControllerSendMessage(debugChannel, { message: `${getTimeStamp()} End reset region process.` });\n    }\n}\nawait main();"
        }
      ],
      "functions": [],
      "permissions": []
    }
  ]
}