{
  "name": "slotMachines",
  "author": "limon",
  "supportedGames": [
    "all"
  ],
  "takaroVersion": "v0.0.24",
  "versions": [
    {
      "tag": "0.0.1",
      "description": "**Limon_slotMachines: Casino-Style Gambling for Your Game Server**\n\nThis module adds an exciting slot machine to your game server, letting players gamble their in-game currency for a chance to win big prizes!\n\n**Key Features:**\n\n![Slots gameplay](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/slotmachines_slots.png)\n\n* **Authentic Casino Experience:** Three-reel slot machine with multiple symbols and varied payouts\n* **Visual Animation:** Progressive reel reveals with suspenseful animations\n* **Big Win Announcements:** Server-wide jackpot notifications for big winners\n* **Statistics Tracking:** Detailed personal gambling stats for each player\n\n**Commands:**\n\n* `/slots [amount]` - Place a bet and spin the slot machine\n* `/slotsstats` - View your personal gambling statistics\n* `/slotsrules` - Check payouts, rules and betting information\n* `/slotsreset [player]` - Admin command to reset player statistics\n\n![Slots rules](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/slotmachines_slotsrules.png)\n\n**Winning Combinations:**\n* Three matching symbols (Three sevens: 25x your bet!)\n* Three bells: 10x your bet\n* Three plums: 6x your bet\n* Three oranges: 4x your bet\n* Three lemons: 3x your bet\n* Three cherries: 2x your bet\n* Any combination ending with seven: 1.5x your bet\n\n**Player Statistics:**\n\n![Slots statistics](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/slotmachines_slotsstats.png)\n\n* Games played, wins, losses, and total currency wagered\n* Win rate percentage calculation\n\n**Configuration:**\n\n![Slot machine configuration](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/slotmachines_userconfig.png)\n\n* `minimumBet`: Lowest possible wager amount\n* `maximumBet`: Highest possible wager amount\n* `houseEdge`: Percentage profit margin for the server\n\n**Permissions:**\n\n* `SLOTS_PLAY`: Basic permission to use the slot machine\n* `SLOTS_HIGHER_LIMITS`: VIP permission allowing higher maximum bets\n* `SLOTS_ADMIN`: Administrative access to reset stats and manage the module",
      "configSchema": "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"required\":[],\"additionalProperties\":false,\"properties\":{\"minimumBet \":{\"title\":\"minimumBet \",\"description\":\"The minimum amount of currency a player can wager on a single spin.\",\"default\":10,\"type\":\"number\",\"minimum\":0},\"maximumBet\":{\"title\":\"maximumBet\",\"description\":\"The maximum amount of currency a player can wager on a single spin.\",\"default\":100,\"type\":\"number\"},\"houseEdge\":{\"title\":\"houseEdge\",\"description\":\"Percentage of winnings the house takes as profit (5 = 5%).\",\"default\":5,\"type\":\"number\"}}}",
      "uiSchema": "{}",
      "commands": [
        {
          "name": "slots",
          "trigger": "slots",
          "helpText": "Play the slot machine with a bet amount.",
          "function": "import { takaro, data, checkPermission, TakaroUserError } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, arguments: args, module: mod, pog } = data;\n\n    // Check permission\n    if (!checkPermission(pog, 'SLOTS_PLAY')) {\n        throw new TakaroUserError('You do not have permission to play slots!');\n    }\n\n    // Get bet amount\n    const betAmount = Number(args.amount);\n\n    // Validate bet amount\n    if (isNaN(betAmount) || betAmount <= 0) {\n        throw new TakaroUserError('Please enter a valid positive bet amount!');\n    }\n\n    // Get minimum and maximum bet limits\n    const minimumBet = mod.userConfig.minimumBet;\n    let maximumBet = mod.userConfig.maximumBet;\n\n    // Check higher limits permission\n    const higherLimitsPermission = checkPermission(pog, 'SLOTS_HIGHER_LIMITS');\n    if (higherLimitsPermission && higherLimitsPermission.count > 0) {\n        maximumBet *= higherLimitsPermission.count;\n    }\n\n    // Validate bet amount against limits\n    if (betAmount < minimumBet) {\n        throw new TakaroUserError(`The minimum bet is ${minimumBet} currency!`);\n    }\n    if (betAmount > maximumBet) {\n        throw new TakaroUserError(`The maximum bet is ${maximumBet} currency!`);\n    }\n\n    // Check if player has enough currency\n    const playerData = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id);\n    const currentBalance = playerData.data.data.currency;\n\n    if (currentBalance < betAmount) {\n        throw new TakaroUserError(`You need ${betAmount} currency to play slots. You only have ${currentBalance}.`);\n    }\n\n    // Deduct the bet amount\n    await takaro.playerOnGameserver.playerOnGameServerControllerDeductCurrency(\n        gameServerId,\n        player.id,\n        {\n            currency: betAmount\n        }\n    );\n\n    // Define slot symbols with their text representations\n    const symbols = [\n        { name: \"cherry\", symbol: \"CH\", weight: 30 },\n        { name: \"lemon\", symbol: \"LE\", weight: 25 },\n        { name: \"orange\", symbol: \"OR\", weight: 20 },\n        { name: \"plum\", symbol: \"PL\", weight: 15 },\n        { name: \"bell\", symbol: \"BE\", weight: 8 },\n        { name: \"seven\", symbol: \"7\", weight: 2 }\n    ];\n\n    // Define payouts for different combinations\n    const payouts = {\n        \"cherry-cherry-cherry\": 2,\n        \"lemon-lemon-lemon\": 3,\n        \"orange-orange-orange\": 4,\n        \"plum-plum-plum\": 6,\n        \"bell-bell-bell\": 10,\n        \"seven-seven-seven\": 25,\n        \"any-any-seven\": 1.5\n    };\n\n    // Show initial message with spinning animation\n    await player.pm(`Betting ${betAmount} currency`);\n    await player.pm(`[SLOTS] [ ?? | ?? | ?? ] Spinning...`);\n\n    // Simulate spinning the reels\n    const results = spinReels(symbols);\n    const resultSymbols = results.map(r => r.name);\n    const resultDisplay = results.map(r => r.symbol);\n\n    // Display reels one by one with suspense\n    await player.pm(`[SLOTS] [ ${resultDisplay[0]} | ?? | ?? ] Spinning...`);\n    await player.pm(`[SLOTS] [ ${resultDisplay[0]} | ${resultDisplay[1]} | ?? ] Spinning...`);\n\n    // Final result with dramatic pause\n    await player.pm(`[SLOTS] [ ${resultDisplay[0]} | ${resultDisplay[1]} | ${resultDisplay[2]} ] !`);\n\n    // Calculate winnings\n    let winMultiplier = 0;\n    let winDescription = \"\";\n\n    // Check for three of a kind\n    if (resultSymbols[0] === resultSymbols[1] && resultSymbols[1] === resultSymbols[2]) {\n        const key = `${resultSymbols[0]}-${resultSymbols[1]}-${resultSymbols[2]}`;\n        winMultiplier = payouts[key] || 0;\n        winDescription = `Three ${resultSymbols[0]}s`;\n    }\n    // Check for \"any-any-seven\" combination\n    else if (resultSymbols[2] === \"seven\") {\n        winMultiplier = payouts[\"any-any-seven\"];\n        winDescription = \"Any combination ending with seven\";\n    }\n\n    // Apply house edge\n    const houseEdge = mod.userConfig.houseEdge / 100;\n    winMultiplier = winMultiplier * (1 - houseEdge);\n\n    // Calculate final winnings\n    const winnings = Math.floor(betAmount * winMultiplier);\n\n    // Update player stats\n    await updatePlayerStats(player.id, gameServerId, mod.moduleId, betAmount, winnings > 0);\n\n    // Handle result with visual flair based on win size\n    if (winnings > 0) {\n        // Add winnings to player\n        await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(\n            gameServerId,\n            player.id,\n            {\n                currency: winnings\n            }\n        );\n\n        // Different win messages based on win size\n        if (winnings >= 1000) {\n            await player.pm(`!!! JACKPOT !!! JACKPOT !!! JACKPOT !!!`);\n            await player.pm(`*** ${winDescription}: +${winnings} CURRENCY! ***`);\n\n            // Broadcast big wins\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: `[SLOTS] JACKPOT! ${player.name} just won ${winnings} currency with ${resultDisplay.join(' ')}`\n            });\n        }\n        else if (winnings >= 500) {\n            await player.pm(`** BIG WIN! **`);\n            await player.pm(`* ${winDescription}: +${winnings} currency! *`);\n        }\n        else {\n            await player.pm(`Winner! ${winDescription}: +${winnings} currency!`);\n        }\n    } else {\n        // Losing message\n        await player.pm(`X No winning combination. Better luck next time! X`);\n    }\n\n    // Get updated balance\n    const updatedPlayerData = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id);\n    await player.pm(`Balance: ${updatedPlayerData.data.data.currency} currency`);\n}\n\n// Function to simulate spinning the reels\nfunction spinReels(symbols) {\n    const results = [];\n\n    for (let i = 0; i < 3; i++) {\n        // Calculate total weight\n        const totalWeight = symbols.reduce((sum, symbol) => sum + symbol.weight, 0);\n\n        // Generate random number\n        let random = Math.random() * totalWeight;\n        let selectedSymbol = null;\n\n        // Select symbol based on weight\n        for (const symbol of symbols) {\n            random -= symbol.weight;\n            if (random <= 0) {\n                selectedSymbol = symbol;\n                break;\n            }\n        }\n\n        results.push(selectedSymbol);\n    }\n\n    return results;\n}\n\nasync function updatePlayerStats(playerId, gameServerId, moduleId, betAmount, won) {\n    const statsKey = 'slots_stats';\n    const statsSearch = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: [statsKey],\n            playerId: [playerId],\n            gameServerId: [gameServerId],\n            moduleId: [moduleId],\n        },\n    });\n\n    let stats = {\n        played: 0,\n        won: 0,\n        lost: 0,\n        totalWagered: 0,\n    };\n\n    if (statsSearch.data.data.length > 0) {\n        stats = JSON.parse(statsSearch.data.data[0].value);\n        await takaro.variable.variableControllerUpdate(statsSearch.data.data[0].id, {\n            value: JSON.stringify({\n                played: stats.played + 1,\n                won: stats.won + (won ? 1 : 0),\n                lost: stats.lost + (won ? 0 : 1),\n                totalWagered: stats.totalWagered + betAmount,\n            }),\n        });\n    } else {\n        await takaro.variable.variableControllerCreate({\n            key: statsKey,\n            value: JSON.stringify({\n                played: 1,\n                won: won ? 1 : 0,\n                lost: won ? 0 : 1,\n                totalWagered: betAmount,\n            }),\n            gameServerId,\n            moduleId: moduleId,\n            playerId: playerId,\n        });\n    }\n}\n\nawait main();",
          "arguments": [
            {
              "name": "amount",
              "type": "number",
              "helpText": "Play the slot machine with a bet amount",
              "defaultValue": "",
              "position": 0
            }
          ]
        },
        {
          "name": "slotsstats",
          "trigger": "slotsstats",
          "helpText": "Shows your slot machine statistics",
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, module: mod } = data;\n\n    const statsKey = 'slots_stats';\n    const statsSearch = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: [statsKey],\n            playerId: [player.id],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId],\n        },\n    });\n\n    if (statsSearch.data.data.length === 0) {\n        await player.pm(\"🎰 You haven't played any slot machine games yet!\");\n        return;\n    }\n\n    const stats = JSON.parse(statsSearch.data.data[0].value);\n    const winRate = stats.played > 0 ? ((stats.won / stats.played) * 100).toFixed(1) : 0;\n\n    // Stats display\n    const statsDisplay = [\n        `🎰 SLOT MACHINE STATS 🎰`,\n        `Games: ${stats.played} (${stats.won} wins, ${stats.lost} losses)`,\n        `Win rate: ${winRate}%`,\n        `Total wagered: ${stats.totalWagered} currency`,\n        `🍀 Good luck on your next spin!`\n    ];\n\n    await player.pm(statsDisplay.join('\\n'));\n}\n\nawait main();",
          "arguments": []
        },
        {
          "name": "slotsreset",
          "trigger": "slotsreset",
          "helpText": "Reset a player's slot machine statistics (admin only)",
          "function": "import { takaro, data, checkPermission, TakaroUserError } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, arguments: args, module: mod, pog } = data;\n\n    // Check admin permission\n    if (!checkPermission(pog, 'SLOTS_ADMIN')) {\n        throw new TakaroUserError('You do not have permission to reset slot machine statistics!');\n    }\n\n    // Get target player\n    if (!args.player) {\n        throw new TakaroUserError('Please specify a player whose stats you want to reset.');\n    }\n\n    const targetPlayerId = args.player.playerId;\n    const targetPlayerName = args.player.name;\n\n    // Find stats to reset\n    const statsKey = 'slots_stats';\n    const statsSearch = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: [statsKey],\n            playerId: [targetPlayerId],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId],\n        },\n    });\n\n    if (statsSearch.data.data.length === 0) {\n        throw new TakaroUserError(`No slot machine statistics found for ${targetPlayerName}.`);\n    }\n\n    // Delete the stats variable\n    await takaro.variable.variableControllerDelete(statsSearch.data.data[0].id);\n\n    // Also delete any cooldown variables\n    const cooldownKey = 'slots_cooldown';\n    const cooldownSearch = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: [cooldownKey],\n            playerId: [targetPlayerId],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId],\n        },\n    });\n\n    if (cooldownSearch.data.data.length > 0) {\n        await takaro.variable.variableControllerDelete(cooldownSearch.data.data[0].id);\n    }\n\n    await player.pm(`✅ Successfully reset slot machine statistics for ${targetPlayerName}.`);\n\n    // Log the action\n    await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, {\n        command: `say [Admin] ${player.name} has reset ${targetPlayerName}'s slot machine statistics.`,\n    });\n}\n\nawait main();",
          "arguments": [
            {
              "name": "player ",
              "type": "string",
              "helpText": "Reset a player's slot machine statistics (admin only)",
              "defaultValue": "",
              "position": 0
            }
          ]
        },
        {
          "name": "slotsrules",
          "trigger": "slotsrules",
          "helpText": "Shows the rules and payouts for the slot machine\n",
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const { player, module: mod } = data;\n\n    const minimumBet = mod.userConfig.minimumBet;\n    const maximumBet = mod.userConfig.maximumBet;\n    const houseEdge = mod.userConfig.houseEdge;\n\n    // Rules introduction\n    const rules1 = [\n        `[SLOTS] SLOT MACHINE RULES`,\n        '',\n        `Minimum bet: ${minimumBet} currency`,\n        `Maximum bet: ${maximumBet} currency`,\n        `House edge: ${houseEdge}%`,\n    ];\n\n    // Symbol information\n    const rules2 = [\n        'SYMBOLS AND PAYOUTS:',\n        '',\n        'CH Three Cherries: 2x your bet',\n        'LE Three Lemons: 3x your bet',\n        'OR Three Oranges: 4x your bet',\n        'PL Three Plums: 6x your bet',\n        'BE Three Bells: 10x your bet',\n        '7  Three Sevens: 25x your bet',\n        'Any combination ending with 7: 1.5x your bet',\n    ];\n\n    // Examples and additional info\n    const rules3 = [\n        'HOW TO PLAY:',\n        '/slots 50 - Bet 50 currency on a spin',\n        '',\n        'Use /slotsstats to view your statistics!'\n    ];\n\n    // Send messages with a slight delay between them\n    await player.pm(rules1.join('\\n'));\n    await player.pm(rules2.join('\\n'));\n    await player.pm(rules3.join('\\n'));\n}\n\nawait main();",
          "arguments": []
        }
      ],
      "hooks": [],
      "cronJobs": [],
      "functions": [],
      "permissions": [
        {
          "permission": "SLOTS_PLAY",
          "friendlyName": "Play Slots",
          "description": "Allows the player to play the slot machine.",
          "canHaveCount": false
        },
        {
          "permission": "SLOTS_HIGHER_LIMITS",
          "friendlyName": "Higher Betting Limits",
          "description": "Allows the player to exceed the normal maximum bet. Count is multiplier for max bet.",
          "canHaveCount": true
        },
        {
          "permission": "SLOTS_ADMIN",
          "friendlyName": "Slots Admin",
          "description": "Allows resetting player statistics and other administrative functions.",
          "canHaveCount": false
        }
      ]
    }
  ]
}